• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

comp-physics / Quantum-HRF-Tomography / 18231523990

03 Oct 2025 07:12PM UTC coverage: 98.151% (+0.2%) from 97.959%
18231523990

push

github

kaminotesf
Add support for non-parametric circuits

279 of 280 new or added lines in 2 files covered. (99.64%)

1 existing line in 1 file now uncovered.

2336 of 2380 relevant lines covered (98.15%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.93
/tests/test_sample.py
1
import unittest
1✔
2
import warnings
1✔
3
import numpy as np
1✔
4
from unittest.mock import Mock, MagicMock, patch
1✔
5
import pytest
1✔
6
from qiskit import QuantumCircuit
1✔
7
from qiskit.circuit.random import random_circuit
1✔
8
from qiskit_aer import AerSimulator
1✔
9
from qiskit.circuit.library import real_amplitudes, efficient_su2
1✔
10
from qiskit_ibm_runtime.fake_provider import FakeFez
1✔
11
from qiskit.providers import JobStatus
1✔
12
from qiskit.result import Result
1✔
13
from hadamard_random_forest.sample import (
1✔
14
    get_statevector,
15
    get_circuits,
16
    get_samples_noisy,
17
    get_circuits_hardware,
18
    get_samples_hardware
19
)
20
from hadamard_random_forest.random_forest import fix_random_seed
1✔
21

22
class TestSample(unittest.TestCase):
1✔
23

24
    def setUp(self):
1✔
25
        """Set up test setting and common test data."""
26
        self.backend_sim = AerSimulator()
1✔
27
        self.fake_backend = FakeFez()
1✔
28
        
29
        # Common test parameters
30
        self.test_qubit_counts = [2, 3, 4]
1✔
31
        self.test_shots = 1024
1✔
32
        
33
        # Create sample circuits for testing
34
        self.simple_circuits = {}
1✔
35
        self.complex_circuits = {}
1✔
36
        
37
        for num_qubits in self.test_qubit_counts:
1✔
38
            # Simple circuit (real_amplitudes)
39
            self.simple_circuits[num_qubits] = real_amplitudes(
1✔
40
                num_qubits, reps=1, insert_barriers=False
41
            )
42
            
43
            # More complex circuit (efficient_su2)
44
            self.complex_circuits[num_qubits] = efficient_su2(
1✔
45
                num_qubits, reps=2, insert_barriers=False
46
            )
47

48
    def _create_test_samples(self, num_qubits: int, normalize: bool = True) -> list:
1✔
49
        """Create valid test sample data."""
50
        samples = [np.random.rand(2**num_qubits) for _ in range(num_qubits + 1)]
1✔
51
        if normalize:
1✔
52
            # Normalize to valid probability distributions
53
            samples = [s / np.sum(s) for s in samples]
1✔
54
        return samples
1✔
55

56
    def _create_mock_counts(self, num_qubits: int, shots: int = 1024) -> dict:
1✔
57
        """Create mock measurement counts for testing."""
58
        counts = {}
1✔
59
        n_states = 2**num_qubits
1✔
60
        # Distribute shots randomly across computational basis states
61
        remaining_shots = shots
1✔
62
        for i in range(n_states - 1):
1✔
63
            count = np.random.randint(0, remaining_shots // (n_states - i) + 1)
1✔
64
            if count > 0:
1✔
65
                counts[format(i, f'0{num_qubits}b')] = count
1✔
66
                remaining_shots -= count
1✔
67
        
68
        # Assign remaining shots to last state
69
        if remaining_shots > 0:
1✔
70
            counts[format(n_states - 1, f'0{num_qubits}b')] = remaining_shots
1✔
71
            
72
        return counts
1✔
73

74
    def test_get_circuits_basic(self):
1✔
75
        """Test basic functionality of get_circuits."""
76
        for num_qubits in self.test_qubit_counts:
1✔
77
            with self.subTest(num_qubits=num_qubits):
1✔
78
                base_circuit = self.simple_circuits[num_qubits]
1✔
79
                circuits = get_circuits(num_qubits, base_circuit)
1✔
80
                
81
                # Verify structure
82
                self.assertIsInstance(circuits, list)
1✔
83
                self.assertEqual(len(circuits), num_qubits + 1)  # Base + H variants
1✔
84
                
85
                # Verify all circuits have correct qubit count
86
                for circuit in circuits:
1✔
87
                    self.assertEqual(circuit.num_qubits, num_qubits)
1✔
88
                    self.assertIsNotNone(circuit)
1✔
89
                    # Verify measurements are present
90
                    self.assertTrue(any(op.operation.name == 'measure' for op in circuit.data))
1✔
91

92
    def test_get_circuits_structure_validation(self):
1✔
93
        """Test that get_circuits preserves base circuit structure correctly."""
94
        num_qubits = 3
1✔
95
        base_circuit = real_amplitudes(num_qubits, reps=2)
1✔
96
        circuits = get_circuits(num_qubits, base_circuit)
1✔
97
        
98
        # Subsequent circuits should have exactly one additional H gate
99
        for i in range(1, len(circuits)):
1✔
100
            circuit = circuits[i]
1✔
101
            
102
            # Count H gates in the circuit
103
            h_count = sum(1 for op in circuit.data if op.operation.name == 'h')
1✔
104
            
105
            # Should have exactly one H gate more than base circuit
106
            # (base circuit shouldn't have H gates for real_amplitudes)
107
            self.assertGreaterEqual(h_count, 1)
1✔
108
            
109
            # Verify the circuit has measurements
110
            measure_count = sum(1 for op in circuit.data if op.operation.name == 'measure')
1✔
111
            self.assertEqual(measure_count, num_qubits)
1✔
112

113
    def test_get_circuits_different_ansatz_types(self):
1✔
114
        """Test get_circuits with different circuit types."""
115
        test_cases = [
1✔
116
            ("real_amplitudes", self.simple_circuits),
117
            ("efficient_su2", self.complex_circuits)
118
        ]
119
        
120
        for ansatz_name, circuit_dict in test_cases:
1✔
121
            for num_qubits in self.test_qubit_counts:
1✔
122
                with self.subTest(ansatz=ansatz_name, num_qubits=num_qubits):
1✔
123
                    base_circuit = circuit_dict[num_qubits]
1✔
124
                    circuits = get_circuits(num_qubits, base_circuit)
1✔
125
                    
126
                    self.assertEqual(len(circuits), num_qubits + 1)
1✔
127
                    
128
                    # Verify parameters are preserved
129
                    if base_circuit.num_parameters > 0:
1✔
130
                        for circuit in circuits:
1✔
131
                            # After composition, parameters should still be present
132
                            # (though measurement might add classical registers)
133
                            self.assertGreaterEqual(circuit.num_parameters, 0)
1✔
134

135
    def test_get_circuits_parameter_preservation(self):
1✔
136
        """Test that circuit parameters are preserved during get_circuits."""
137
        num_qubits = 3
1✔
138
        base_circuit = real_amplitudes(num_qubits, reps=2)
1✔
139
        original_params = base_circuit.num_parameters
1✔
140
        
141
        circuits = get_circuits(num_qubits, base_circuit)
1✔
142
        
143
        # All circuits should preserve the original parameters
144
        for circuit in circuits:
1✔
145
            self.assertEqual(circuit.num_parameters, original_params)
1✔
146

147
    def test_get_samples_noisy_without_mitigation(self):
1✔
148
        """Test get_samples_noisy without error mitigation."""
149
        for num_qubits in [2, 3]:  # Use smaller systems for faster testing
1✔
150
            with self.subTest(num_qubits=num_qubits):
1✔
151
                base_circuit = self.simple_circuits[num_qubits]
1✔
152
                circuits = get_circuits(num_qubits, base_circuit)
1✔
153
                parameters = np.random.rand(base_circuit.num_parameters)
1✔
154
                
155
                samples = get_samples_noisy(
1✔
156
                    num_qubits=num_qubits,
157
                    circuits=circuits,
158
                    shots=self.test_shots,
159
                    parameters=parameters,
160
                    backend_sim=self.backend_sim,
161
                    error_mitigation=False
162
                )
163
                
164
                # Verify output structure
165
                self.assertIsInstance(samples, list)
1✔
166
                self.assertEqual(len(samples), num_qubits + 1)
1✔
167
                
168
                # Verify each sample array
169
                for sample in samples:
1✔
170
                    self.assertIsInstance(sample, np.ndarray)
1✔
171
                    self.assertEqual(sample.shape, (2**num_qubits,))
1✔
172
                    self.assertTrue(np.all(sample >= 0))  # Probabilities non-negative
1✔
173
                    self.assertAlmostEqual(np.sum(sample), 1.0, places=6)  # Normalized
1✔
174

175
    @patch('hadamard_random_forest.sample.M3Mitigation')
1✔
176
    @patch('hadamard_random_forest.sample.mthree_utils.final_measurement_mapping')
1✔
177
    def test_get_samples_noisy_with_mitigation(self, mock_mapping, mock_m3):
1✔
178
        """Test get_samples_noisy with error mitigation using mocks."""
179
        num_qubits = 2
1✔
180
        base_circuit = self.simple_circuits[num_qubits]
1✔
181
        circuits = get_circuits(num_qubits, base_circuit)
1✔
182
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
183
        
184
        # Mock the M3 mitigation objects
185
        mock_mit_instance = MagicMock()
1✔
186
        mock_m3.return_value = mock_mit_instance
1✔
187
        
188
        # Mock mapping function
189
        mock_mapping.return_value = [0, 1]
1✔
190
        
191
        # Mock the mitigation correction
192
        mock_quasi = MagicMock()
1✔
193
        mock_quasi.nearest_probability_distribution.return_value = {
1✔
194
            '00': 0.25, '01': 0.25, '10': 0.25, '11': 0.25
195
        }
196
        mock_mit_instance.apply_correction.return_value = mock_quasi
1✔
197
        
198
        # Mock the backend to return predictable counts
199
        mock_job = MagicMock()
1✔
200
        mock_result = MagicMock()
1✔
201
        mock_job.result.return_value = mock_result
1✔
202
        mock_result.get_counts.return_value = self._create_mock_counts(num_qubits, self.test_shots)
1✔
203
        
204
        with patch.object(self.backend_sim, 'run', return_value=mock_job):
1✔
205
            samples = get_samples_noisy(
1✔
206
                num_qubits=num_qubits,
207
                circuits=circuits,
208
                shots=self.test_shots,
209
                parameters=parameters,
210
                backend_sim=self.backend_sim,
211
                error_mitigation=True
212
            )
213
        
214
        # Verify mitigation was called
215
        self.assertTrue(mock_m3.called)
1✔
216
        self.assertTrue(mock_mit_instance.apply_correction.called)
1✔
217
        
218
        # Verify output structure
219
        self.assertIsInstance(samples, list)
1✔
220
        self.assertEqual(len(samples), num_qubits + 1)
1✔
221
        
222
        for sample in samples:
1✔
223
            self.assertIsInstance(sample, np.ndarray)
1✔
224
            self.assertEqual(sample.shape, (2**num_qubits,))
1✔
225

226
    def test_get_samples_noisy_different_shot_counts(self):
1✔
227
        """Test get_samples_noisy with different shot counts."""
228
        num_qubits = 2
1✔
229
        base_circuit = self.simple_circuits[num_qubits]
1✔
230
        circuits = get_circuits(num_qubits, base_circuit)
1✔
231
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
232
        
233
        shot_counts = [100, 1000, 10000]
1✔
234
        
235
        for shots in shot_counts:
1✔
236
            with self.subTest(shots=shots):
1✔
237
                samples = get_samples_noisy(
1✔
238
                    num_qubits=num_qubits,
239
                    circuits=circuits,
240
                    shots=shots,
241
                    parameters=parameters,
242
                    backend_sim=self.backend_sim,
243
                    error_mitigation=False
244
                )
245
                
246
                # Basic validation
247
                self.assertEqual(len(samples), num_qubits + 1)
1✔
248
                for sample in samples:
1✔
249
                    self.assertEqual(sample.shape, (2**num_qubits,))
1✔
250
                    # Higher shot counts should generally give more precise results
251
                    self.assertTrue(np.all(sample >= 0))
1✔
252

253
    def test_get_samples_noisy_parameter_assignment(self):
1✔
254
        """Test that parameters are correctly assigned to circuits."""
255
        num_qubits = 3
1✔
256
        base_circuit = self.simple_circuits[num_qubits]
1✔
257
        circuits = get_circuits(num_qubits, base_circuit)
1✔
258

259
        # Test with different parameter values
260
        param_sets = [
1✔
261
            np.zeros(base_circuit.num_parameters),
262
            np.ones(base_circuit.num_parameters) * 0.5,
263
            np.random.rand(base_circuit.num_parameters) * 2 * np.pi
264
        ]
265

266
        for i, parameters in enumerate(param_sets):
1✔
267
            with self.subTest(param_set=i):
1✔
268
                samples = get_samples_noisy(
1✔
269
                    num_qubits=num_qubits,
270
                    circuits=circuits,
271
                    shots=self.test_shots,
272
                    parameters=parameters,
273
                    backend_sim=self.backend_sim,
274
                    error_mitigation=False
275
                )
276

277
                # Different parameters should generally produce different results
278
                self.assertEqual(len(samples), num_qubits + 1)
1✔
279
                for sample in samples:
1✔
280
                    self.assertTrue(np.isfinite(sample).all())
1✔
281

282
    @patch('hadamard_random_forest.sample.M3Mitigation')
1✔
283
    @patch('hadamard_random_forest.sample.mthree_utils.final_measurement_mapping')
1✔
284
    def test_get_samples_noisy_prebound_with_mitigation(self, mock_mapping, mock_m3):
1✔
285
        """Test get_samples_noisy with pre-bound circuits AND error mitigation."""
286
        num_qubits = 2
1✔
287
        base_circuit = self.simple_circuits[num_qubits]
1✔
288

289
        # Bind parameters to the circuit first
290
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
291
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
292
        circuits = get_circuits(num_qubits, bound_circuit)
1✔
293

294
        # Mock M3 mitigation
295
        mock_mit_instance = MagicMock()
1✔
296
        mock_m3.return_value = mock_mit_instance
1✔
297
        mock_mapping.return_value = [0, 1]
1✔
298

299
        mock_quasi = MagicMock()
1✔
300
        mock_quasi.nearest_probability_distribution.return_value = {
1✔
301
            '00': 0.25, '01': 0.25, '10': 0.25, '11': 0.25
302
        }
303
        mock_mit_instance.apply_correction.return_value = mock_quasi
1✔
304

305
        # Mock the backend
306
        mock_job = MagicMock()
1✔
307
        mock_result = MagicMock()
1✔
308
        mock_job.result.return_value = mock_result
1✔
309
        mock_result.get_counts.return_value = self._create_mock_counts(num_qubits, 1024)
1✔
310

311
        with patch.object(self.backend_sim, 'run', return_value=mock_job):
1✔
312
            # Call with None parameters since circuit is pre-bound
313
            samples = get_samples_noisy(
1✔
314
                num_qubits=num_qubits,
315
                circuits=circuits,
316
                shots=1024,
317
                parameters=None,
318
                backend_sim=self.backend_sim,
319
                error_mitigation=True
320
            )
321

322
        # Verify mitigation was applied
323
        self.assertTrue(mock_m3.called)
1✔
324
        self.assertTrue(mock_mit_instance.apply_correction.called)
1✔
325

326
        # Verify output structure
327
        self.assertIsInstance(samples, list)
1✔
328
        self.assertEqual(len(samples), num_qubits + 1)
1✔
329

330
        for sample in samples:
1✔
331
            self.assertIsInstance(sample, np.ndarray)
1✔
332
            self.assertEqual(sample.shape, (2**num_qubits,))
1✔
333
            self.assertTrue(np.all(sample >= 0))
1✔
334

335
    def test_get_circuits_hardware_basic(self):
1✔
336
        """Test basic functionality of get_circuits_hardware."""
337
        for num_qubits in [2, 3]:  # Use smaller systems for transpilation
1✔
338
            with self.subTest(num_qubits=num_qubits):
1✔
339
                base_circuit = self.simple_circuits[num_qubits]
1✔
340
                
341
                circuits = get_circuits_hardware(
1✔
342
                    num_qubits=num_qubits,
343
                    base_circuit=base_circuit,
344
                    device=self.fake_backend
345
                )
346
                
347
                # Verify structure
348
                self.assertIsInstance(circuits, list)
1✔
349
                self.assertEqual(len(circuits), num_qubits + 1)
1✔
350
                
351
                # Verify all circuits are transpiled (should have different structure)
352
                for circuit in circuits:
1✔
353
                    self.assertIsNotNone(circuit)
1✔
354
                    # Transpiled circuits may have different qubit counts due to routing
355
                    self.assertGreaterEqual(circuit.num_qubits, num_qubits)
1✔
356
                    # Verify measurements are present
357
                    self.assertTrue(any(op.operation.name == 'measure' for op in circuit.data))
1✔
358

359
    def test_get_circuits_hardware_transpilation(self):
1✔
360
        """Test that transpilation occurs correctly."""
361
        num_qubits = 3
1✔
362
        base_circuit = self.simple_circuits[num_qubits]
1✔
363
        
364
        # Compare transpiled vs non-transpiled
365
        regular_circuits = get_circuits(num_qubits, base_circuit)
1✔
366
        hardware_circuits = get_circuits_hardware(
1✔
367
            num_qubits=num_qubits,
368
            base_circuit=base_circuit,
369
            device=self.fake_backend
370
        )
371
        
372
        # Same number of circuits
373
        self.assertEqual(len(regular_circuits), len(hardware_circuits))
1✔
374
        
375
        # Hardware circuits should generally have more gates due to decomposition
376
        for i, (reg_circuit, hw_circuit) in enumerate(zip(regular_circuits, hardware_circuits)):
1✔
377
            with self.subTest(circuit_index=i):
1✔
378
                # Hardware circuits may have more operations due to transpilation
379
                self.assertGreaterEqual(len(hw_circuit.data), 0)
1✔
380
                # Both should have measurements
381
                reg_has_measure = any(op.operation.name == 'measure' for op in reg_circuit.data)
1✔
382
                hw_has_measure = any(op.operation.name == 'measure' for op in hw_circuit.data)
1✔
383
                self.assertTrue(reg_has_measure)
1✔
384
                self.assertTrue(hw_has_measure)
1✔
385

386
    def test_get_circuits_hardware_different_backends(self):
1✔
387
        """Test get_circuits_hardware with different backend types."""
388
        num_qubits = 2
1✔
389
        base_circuit = self.simple_circuits[num_qubits]
1✔
390
        
391
        # Test with different backends
392
        backends = [
1✔
393
            self.fake_backend,
394
            AerSimulator.from_backend(self.fake_backend)
395
        ]
396
        
397
        for i, backend in enumerate(backends):
1✔
398
            with self.subTest(backend_type=i):
1✔
399
                circuits = get_circuits_hardware(
1✔
400
                    num_qubits=num_qubits,
401
                    base_circuit=base_circuit,
402
                    device=backend
403
                )
404
                
405
                self.assertEqual(len(circuits), num_qubits + 1)
1✔
406
                for circuit in circuits:
1✔
407
                    self.assertIsNotNone(circuit)
1✔
408
                    # Verify the circuit is executable (has valid structure)
409
                    self.assertGreater(len(circuit.data), 0)
1✔
410

411
    @patch('hadamard_random_forest.sample.generate_preset_pass_manager')
1✔
412
    def test_get_circuits_hardware_pass_manager_usage(self, mock_pm):
1✔
413
        """Test that pass manager is used correctly."""
414
        num_qubits = 2
1✔
415
        base_circuit = self.simple_circuits[num_qubits]
1✔
416
        
417
        # Mock the pass manager
418
        mock_pm_instance = MagicMock()
1✔
419
        mock_pm.return_value = mock_pm_instance
1✔
420
        mock_pm_instance.run.side_effect = lambda x: x  # Return circuit unchanged
1✔
421
        
422
        circuits = get_circuits_hardware(
1✔
423
            num_qubits=num_qubits,
424
            base_circuit=base_circuit,
425
            device=self.fake_backend
426
        )
427
        
428
        # Verify pass manager was created and used
429
        mock_pm.assert_called_once()
1✔
430
        # Should be called once for each circuit (num_qubits + 1)
431
        self.assertEqual(mock_pm_instance.run.call_count, num_qubits + 1)
1✔
432
        # Verify we got the expected number of circuits
433
        self.assertEqual(len(circuits), num_qubits + 1)
1✔
434

435
    @patch('hadamard_random_forest.sample.Sampler')
1✔
436
    @patch('hadamard_random_forest.sample.mthree_utils.final_measurement_mapping')
1✔
437
    def test_get_samples_hardware_basic(self, mock_mapping, mock_sampler_class):
1✔
438
        """Test basic functionality of get_samples_hardware."""
439
        num_qubits = 2
1✔
440
        base_circuit = self.simple_circuits[num_qubits]
1✔
441
        circuits = get_circuits_hardware(num_qubits, base_circuit, self.fake_backend)
1✔
442
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
443
        shots = 1024
1✔
444
        
445
        # Mock the sampler and its results
446
        mock_sampler = MagicMock()
1✔
447
        mock_sampler_class.return_value = mock_sampler
1✔
448
        
449
        # Mock job and result
450
        mock_job = MagicMock()
1✔
451
        mock_result = MagicMock()
1✔
452
        mock_data = MagicMock()
1✔
453
        mock_meas = MagicMock()
1✔
454
        
455
        # Set up the mock chain
456
        mock_sampler.run.return_value = mock_job
1✔
457
        mock_job.result.return_value = [mock_result]
1✔
458
        mock_result.data = mock_data
1✔
459
        mock_data.meas = mock_meas
1✔
460
        mock_meas.get_counts.return_value = self._create_mock_counts(num_qubits, shots)
1✔
461
        mock_job.job_id.return_value = f"job_test_{np.random.randint(1000)}"
1✔
462
        mock_job.usage_estimation = {'quantum_seconds': 1.23}
1✔
463
        
464
        # Mock mapping
465
        mock_mapping.return_value = list(range(num_qubits))
1✔
466
        
467
        result = get_samples_hardware(
1✔
468
            num_qubits=num_qubits,
469
            shots=shots,
470
            circuits=circuits,
471
            parameters=parameters,
472
            device=self.fake_backend,
473
            error_mitigation=False
474
        )
475
        
476
        # Verify return structure
477
        self.assertIsInstance(result, tuple)
1✔
478
        self.assertEqual(len(result), 4)
1✔
479
        
480
        mitigated_samples, raw_samples, job_ids, quantum_times = result
1✔
481
        
482
        # Verify samples
483
        self.assertIsInstance(mitigated_samples, list)
1✔
484
        self.assertIsInstance(raw_samples, list)
1✔
485
        self.assertEqual(len(mitigated_samples), num_qubits + 1)
1✔
486
        self.assertEqual(len(raw_samples), num_qubits + 1)
1✔
487
        
488
        for sample in mitigated_samples + raw_samples:
1✔
489
            self.assertIsInstance(sample, np.ndarray)
1✔
490
            self.assertEqual(sample.shape, (2**num_qubits,))
1✔
491
            self.assertTrue(np.all(sample >= 0))
1✔
492
        
493
        # Verify job metadata
494
        self.assertIsInstance(job_ids, list)
1✔
495
        self.assertIsInstance(quantum_times, list)
1✔
496
        self.assertEqual(len(job_ids), num_qubits + 1)
1✔
497
        self.assertEqual(len(quantum_times), num_qubits + 1)
1✔
498

499
    @patch('hadamard_random_forest.sample.Sampler')
1✔
500
    @patch('hadamard_random_forest.sample.mthree.M3Mitigation')
1✔
501
    @patch('hadamard_random_forest.sample.mthree_utils.final_measurement_mapping')
1✔
502
    def test_get_samples_hardware_with_mitigation(self, mock_mapping, mock_m3, mock_sampler_class):
1✔
503
        """Test get_samples_hardware with error mitigation."""
504
        num_qubits = 2
1✔
505
        base_circuit = self.simple_circuits[num_qubits]
1✔
506
        circuits = get_circuits_hardware(num_qubits, base_circuit, self.fake_backend)
1✔
507
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
508
        shots = 1024
1✔
509
        
510
        # Mock sampler
511
        mock_sampler = MagicMock()
1✔
512
        mock_sampler_class.return_value = mock_sampler
1✔
513
        
514
        # Mock job results
515
        mock_job = MagicMock()
1✔
516
        mock_result = MagicMock()
1✔
517
        mock_data = MagicMock()
1✔
518
        mock_meas = MagicMock()
1✔
519
        
520
        mock_sampler.run.return_value = mock_job
1✔
521
        mock_job.result.return_value = [mock_result]
1✔
522
        mock_result.data = mock_data
1✔
523
        mock_data.meas = mock_meas
1✔
524
        mock_meas.get_counts.return_value = self._create_mock_counts(num_qubits, shots)
1✔
525
        mock_job.job_id.return_value = "test_job_with_mitigation"
1✔
526
        mock_job.usage_estimation = {'quantum_seconds': 2.45}
1✔
527
        
528
        # Mock M3 mitigation
529
        mock_mit_instance = MagicMock()
1✔
530
        mock_m3.return_value = mock_mit_instance
1✔
531
        mock_quasi = MagicMock()
1✔
532
        mock_quasi.nearest_probability_distribution.return_value = {
1✔
533
            '00': 0.25, '01': 0.25, '10': 0.25, '11': 0.25
534
        }
535
        mock_mit_instance.apply_correction.return_value = mock_quasi
1✔
536
        
537
        # Mock mapping
538
        mock_mapping.return_value = list(range(num_qubits))
1✔
539
        
540
        result = get_samples_hardware(
1✔
541
            num_qubits=num_qubits,
542
            shots=shots,
543
            circuits=circuits,
544
            parameters=parameters,
545
            device=self.fake_backend,
546
            error_mitigation=True
547
        )
548
        
549
        # Verify mitigation was used
550
        self.assertTrue(mock_m3.called)
1✔
551
        self.assertTrue(mock_mit_instance.apply_correction.called)
1✔
552
        
553
        # Verify structure
554
        mitigated_samples, raw_samples, job_ids, quantum_times = result
1✔
555
        self.assertEqual(len(mitigated_samples), num_qubits + 1)
1✔
556
        self.assertEqual(len(raw_samples), num_qubits + 1)
1✔
557
        self.assertEqual(len(job_ids), num_qubits + 1)
1✔
558
        self.assertEqual(len(quantum_times), num_qubits + 1)
1✔
559
        
560
        # Mitigated and raw samples should be different arrays
561
        for i in range(len(mitigated_samples)):
1✔
562
            # They should have the same shape but potentially different values
563
            self.assertEqual(mitigated_samples[i].shape, raw_samples[i].shape)
1✔
564

565
    @patch('hadamard_random_forest.sample.Sampler')
1✔
566
    def test_get_samples_hardware_job_tracking(self, mock_sampler_class):
1✔
567
        """Test that job IDs and quantum times are correctly tracked."""
568
        num_qubits = 2
1✔
569
        base_circuit = self.simple_circuits[num_qubits]
1✔
570
        circuits = get_circuits_hardware(num_qubits, base_circuit, self.fake_backend)
1✔
571
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
572
        
573
        # Mock sampler with unique job IDs and times
574
        mock_sampler = MagicMock()
1✔
575
        mock_sampler_class.return_value = mock_sampler
1✔
576
        
577
        # Create unique job mocks
578
        job_ids = [f"job_{i}" for i in range(num_qubits + 1)]
1✔
579
        quantum_times = [i * 0.5 + 1.0 for i in range(num_qubits + 1)]
1✔
580
        
581
        def create_mock_job(job_id, q_time):
1✔
582
            mock_job = MagicMock()
1✔
583
            mock_result = MagicMock()
1✔
584
            mock_data = MagicMock()
1✔
585
            mock_meas = MagicMock()
1✔
586
            
587
            mock_job.result.return_value = [mock_result]
1✔
588
            mock_result.data = mock_data
1✔
589
            mock_data.meas = mock_meas
1✔
590
            mock_meas.get_counts.return_value = self._create_mock_counts(num_qubits, 1024)
1✔
591
            mock_job.job_id.return_value = job_id
1✔
592
            mock_job.usage_estimation = {'quantum_seconds': q_time}
1✔
593
            
594
            return mock_job
1✔
595
        
596
        # Set up sampler to return different jobs
597
        mock_jobs = [create_mock_job(jid, qt) for jid, qt in zip(job_ids, quantum_times)]
1✔
598
        mock_sampler.run.side_effect = mock_jobs
1✔
599
        
600
        result = get_samples_hardware(
1✔
601
            num_qubits=num_qubits,
602
            shots=1024,
603
            circuits=circuits,
604
            parameters=parameters,
605
            device=self.fake_backend,
606
            error_mitigation=False
607
        )
608
        
609
        _, _, returned_job_ids, returned_quantum_times = result
1✔
610
        
611
        # Verify job tracking
612
        self.assertEqual(returned_job_ids, job_ids)
1✔
613
        self.assertEqual(returned_quantum_times, quantum_times)
1✔
614

615
    def test_get_statevector_basic(self):
1✔
616
        """Test basic functionality of get_statevector."""
617
        for num_qubits in [2, 3]:
1✔
618
            with self.subTest(num_qubits=num_qubits):
1✔
619
                num_trees = 5
1✔
620
                samples = self._create_test_samples(num_qubits, normalize=True)
1✔
621
                
622
                statevector = get_statevector(
1✔
623
                    num_qubits=num_qubits,
624
                    num_trees=num_trees,
625
                    samples=samples,
626
                    save_tree=False,
627
                    show_tree=False
628
                )
629
                
630
                # Verify structure
631
                self.assertIsInstance(statevector, np.ndarray)
1✔
632
                self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
633
                
634
                # Verify properties
635
                self.assertTrue(np.all(np.isfinite(statevector)))
1✔
636
                self.assertGreater(np.linalg.norm(statevector), 0)
1✔
637
                # Should be normalized
638
                self.assertAlmostEqual(np.linalg.norm(statevector), 1.0, places=6)
1✔
639

640
    def test_get_statevector_negative_probabilities_warning(self):
1✔
641
        """Test that get_statevector handles negative probabilities with warning."""
642
        num_qubits = 2
1✔
643
        num_trees = 3
1✔
644
        
645
        # Create samples with some negative values
646
        samples = self._create_test_samples(num_qubits, normalize=False)
1✔
647
        samples[0][1] = -0.1  # Add negative value to first sample
1✔
648
        
649
        with warnings.catch_warnings(record=True) as w:
1✔
650
            warnings.simplefilter("always")
1✔
651
            statevector = get_statevector(
1✔
652
                num_qubits=num_qubits,
653
                num_trees=num_trees,
654
                samples=samples,
655
                save_tree=False,
656
                show_tree=False
657
            )
658
            
659
            # Check that warning was issued
660
            self.assertEqual(len(w), 1)
1✔
661
            self.assertTrue(issubclass(w[0].category, UserWarning))
1✔
662
            self.assertIn("Negative sample probabilities", str(w[0].message))
1✔
663
        
664
        # Result should still be valid
665
        self.assertIsInstance(statevector, np.ndarray)
1✔
666
        self.assertTrue(np.all(np.isfinite(statevector)))
1✔
667

668

669
    def test_get_statevector_different_tree_counts(self):
1✔
670
        """Test get_statevector with different numbers of trees."""
671
        num_qubits = 2
1✔
672
        samples = self._create_test_samples(num_qubits, normalize=True)
1✔
673
        
674
        tree_counts = [1, 3, 5, 11]  # Include odd numbers for majority voting
1✔
675
        
676
        for num_trees in tree_counts:
1✔
677
            with self.subTest(num_trees=num_trees):
1✔
678
                with warnings.catch_warnings():
1✔
679
                    warnings.simplefilter("ignore", UserWarning)
1✔
680
                    statevector = get_statevector(
1✔
681
                        num_qubits=num_qubits,
682
                        num_trees=num_trees,
683
                        samples=samples,
684
                        save_tree=False,
685
                        show_tree=False
686
                    )
687
                
688
                self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
689
                self.assertAlmostEqual(np.linalg.norm(statevector), 1.0, places=6)
1✔
690

691
    def test_get_statevector_normalization(self):
1✔
692
        """Test that get_statevector properly normalizes output."""
693
        num_qubits = 3
1✔
694
        num_trees = 5
1✔
695
        samples = self._create_test_samples(num_qubits, normalize=True)
1✔
696
        
697
        with warnings.catch_warnings():
1✔
698
            warnings.simplefilter("ignore", UserWarning)
1✔
699
            statevector = get_statevector(
1✔
700
                num_qubits=num_qubits,
701
                num_trees=num_trees,
702
                samples=samples,
703
                save_tree=False,
704
                show_tree=False
705
            )
706
        
707
        # Test normalization
708
        norm = np.linalg.norm(statevector)
1✔
709
        self.assertAlmostEqual(norm, 1.0, places=6)
1✔
710
        
711
        # Test that all elements are reasonable
712
        self.assertTrue(np.all(np.abs(statevector) <= 1.0))
1✔
713

714
    def test_get_statevector_reproducibility(self):
1✔
715
        """Test that get_statevector gives reproducible results with same input."""
716
        num_qubits = 2
1✔
717
        num_trees = 5
1✔
718
        samples = self._create_test_samples(num_qubits, normalize=True)
1✔
719
        
720
        # Run twice with same samples
721
        with warnings.catch_warnings():
1✔
722
            warnings.simplefilter("ignore", UserWarning)
1✔
723
            
724
            # Fix the random seed before each call
725
            fix_random_seed(42)
1✔
726
            statevector1 = get_statevector(
1✔
727
                num_qubits=num_qubits,
728
                num_trees=num_trees,
729
                samples=samples.copy(),
730
                save_tree=False,
731
                show_tree=False
732
            )
733
            
734
            fix_random_seed(42)
1✔
735
            statevector2 = get_statevector(
1✔
736
                num_qubits=num_qubits,
737
                num_trees=num_trees,
738
                samples=samples.copy(),
739
                save_tree=False,
740
                show_tree=False
741
            )
742
        
743
        # Results should be identical with same seed
744
        np.testing.assert_array_almost_equal(statevector1, statevector2, decimal=10)
1✔
745

746

747
class TestSampleErrorHandling(unittest.TestCase):
1✔
748
    """Test error handling and input validation for sample functions."""
749

750
    def setUp(self):
1✔
751
        """Set up test fixtures."""
752
        self.backend_sim = AerSimulator()
1✔
753
        self.fake_backend = FakeFez()
1✔
754

755
    def test_get_circuits_invalid_inputs(self):
1✔
756
        """Test get_circuits with edge case inputs."""
757
        # Test with negative qubits - should work but produce empty range
758
        try:
1✔
759
            circuits = get_circuits(-1, QuantumCircuit(1))
1✔
760
            # Should return just the base circuit with measurements
761
            self.assertEqual(len(circuits), 1)  # -1 + 1 = 0 additional circuits
1✔
762
        except Exception:
×
763
            # Negative qubits might raise an exception, which is also acceptable
764
            pass
×
765
        
766
        # Test with mismatched qubit count - function works but may produce unexpected results
767
        circuit_2q = QuantumCircuit(2)
1✔
768
        circuits = get_circuits(3, circuit_2q)  # Circuit has 2 qubits, asking for 3
1✔
769
        # Should still return 4 circuits (3+1), though the extra H gates will be on non-existent qubits
770
        self.assertEqual(len(circuits), 4)
1✔
771

772
    def test_get_samples_noisy_invalid_parameters(self):
1✔
773
        """Test get_samples_noisy with invalid parameter arrays."""
774
        num_qubits = 2
1✔
775
        base_circuit = real_amplitudes(num_qubits, reps=1)
1✔
776
        circuits = get_circuits(num_qubits, base_circuit)
1✔
777
        
778
        # Test with wrong parameter count
779
        wrong_params = np.random.rand(base_circuit.num_parameters + 1)
1✔
780
        
781
        with self.assertRaises(Exception):
1✔
782
            get_samples_noisy(
1✔
783
                num_qubits=num_qubits,
784
                circuits=circuits,
785
                shots=1024,
786
                parameters=wrong_params,
787
                backend_sim=self.backend_sim,
788
                error_mitigation=False
789
            )
790

791
    def test_get_samples_noisy_zero_shots(self):
1✔
792
        """Test get_samples_noisy with zero shots."""
793
        num_qubits = 2
1✔
794
        base_circuit = real_amplitudes(num_qubits, reps=1)
1✔
795
        circuits = get_circuits(num_qubits, base_circuit)
1✔
796
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
797
        
798
        # Zero shots should either raise error or return empty results
799
        with self.assertRaises(Exception):
1✔
800
            get_samples_noisy(
1✔
801
                num_qubits=num_qubits,
802
                circuits=circuits,
803
                shots=0,
804
                parameters=parameters,
805
                backend_sim=self.backend_sim,
806
                error_mitigation=False
807
            )
808

809
    def test_get_statevector_invalid_samples(self):
1✔
810
        """Test get_statevector with invalid sample inputs."""
811
        num_qubits = 2
1✔
812
        num_trees = 3
1✔
813
        
814
        # Test with wrong number of samples
815
        wrong_samples = [np.random.rand(4) for _ in range(2)]  # Should be 3 samples for 2 qubits
1✔
816
        
817
        with self.assertRaises(Exception):
1✔
818
            get_statevector(num_qubits, num_trees, wrong_samples, save_tree=False)
1✔
819
        
820
        # Test with wrong sample dimensions
821
        wrong_dim_samples = [np.random.rand(8) for _ in range(3)]  # Should be 4 elements for 2 qubits
1✔
822
        
823
        with self.assertRaises(Exception):
1✔
824
            get_statevector(num_qubits, num_trees, wrong_dim_samples, save_tree=False)
1✔
825

826
    def test_get_statevector_empty_samples(self):
1✔
827
        """Test get_statevector with empty or null samples."""
828
        num_qubits = 2
1✔
829
        num_trees = 3
1✔
830

831
        # Test with empty list
832
        with self.assertRaises(Exception):
1✔
833
            get_statevector(num_qubits, num_trees, [], save_tree=False)
1✔
834

835
        # Test with samples containing zeros - should raise ValueError for zero norm
836
        zero_samples = [np.zeros(4) for _ in range(3)]
1✔
837

838
        with self.assertRaises(ValueError) as context:
1✔
839
            get_statevector(num_qubits, num_trees, zero_samples, save_tree=False)
1✔
840

841
        # Verify error message is informative
842
        self.assertIn("zero norm", str(context.exception).lower())
1✔
843
        self.assertIn("reconstruction failed", str(context.exception).lower())
1✔
844

845
    def test_get_statevector_zero_norm_raises_error(self):
1✔
846
        """Test that get_statevector raises ValueError for zero-norm statevectors."""
847
        num_qubits = 2
1✔
848
        num_trees = 5
1✔
849

850
        # Create samples that will result in zero statevector
851
        # All zeros in base sample
852
        zero_base = np.zeros(2**num_qubits)
1✔
853
        other_samples = [np.random.rand(2**num_qubits) for _ in range(num_qubits)]
1✔
854
        samples = [zero_base] + other_samples
1✔
855

856
        with self.assertRaises(ValueError) as context:
1✔
857
            get_statevector(num_qubits, num_trees, samples, save_tree=False)
1✔
858

859
        # Verify the error message contains helpful information
860
        error_msg = str(context.exception)
1✔
861
        self.assertIn("zero norm", error_msg.lower())
1✔
862
        self.assertIn("reconstruction failed", error_msg.lower())
1✔
863
        self.assertIn("sample", error_msg.lower())
1✔
864

865
    def test_get_statevector_near_zero_norm_threshold(self):
1✔
866
        """Test statevector reconstruction near the zero-norm threshold (1e-12)."""
867
        num_qubits = 2
1✔
868
        num_trees = 3
1✔
869

870
        # Create samples with very small but non-zero values (above threshold)
871
        small_value = 1e-10
1✔
872
        base_sample = np.ones(2**num_qubits) * small_value
1✔
873
        base_sample = base_sample / np.sum(base_sample)
1✔
874

875
        other_samples = [np.random.rand(2**num_qubits) for _ in range(num_qubits)]
1✔
876
        other_samples = [s / np.sum(s) for s in other_samples]
1✔
877
        samples = [base_sample] + other_samples
1✔
878

879
        # Should succeed since norm will be > 1e-12 after sqrt and majority voting
880
        with warnings.catch_warnings():
1✔
881
            warnings.simplefilter("ignore", UserWarning)
1✔
882
            try:
1✔
883
                statevector = get_statevector(num_qubits, num_trees, samples, save_tree=False)
1✔
884
                self.assertIsInstance(statevector, np.ndarray)
1✔
885
                self.assertAlmostEqual(np.linalg.norm(statevector), 1.0, places=6)
1✔
NEW
886
            except ValueError:
×
UNCOV
887
                pass
×
888

889
        # Create samples that will be below threshold - use all zeros for guaranteed zero norm
890
        zero_sample = np.zeros(2**num_qubits)
1✔
891
        zero_samples = [zero_sample] + [np.random.rand(2**num_qubits) for _ in range(num_qubits)]
1✔
892

893
        # Should raise ValueError for effectively zero norm
894
        with self.assertRaises(ValueError) as context:
1✔
895
            get_statevector(num_qubits, num_trees, zero_samples, save_tree=False)
1✔
896

897
        self.assertIn("zero norm", str(context.exception).lower())
1✔
898

899
    def test_get_circuits_hardware_invalid_backend(self):
1✔
900
        """Test get_circuits_hardware with invalid backend."""
901
        num_qubits = 2
1✔
902
        base_circuit = real_amplitudes(num_qubits)
1✔
903
        
904
        # Test with None backend - may raise exception when passed to generate_preset_pass_manager
905
        try:
1✔
906
            circuits = get_circuits_hardware(num_qubits, base_circuit, None)
1✔
907
            # If it doesn't raise an exception, that's also acceptable behavior
908
            self.assertIsInstance(circuits, list)
1✔
909
        except Exception:
×
910
            # None backend should raise an exception in generate_preset_pass_manager
911
            pass
×
912

913
    @patch('hadamard_random_forest.sample.Sampler')
1✔
914
    def test_get_samples_hardware_failed_jobs(self, mock_sampler_class):
1✔
915
        """Test get_samples_hardware handling of failed jobs."""
916
        num_qubits = 2
1✔
917
        base_circuit = real_amplitudes(num_qubits)
1✔
918
        
919
        # Suppress mthree deprecation warnings from external library
920
        with warnings.catch_warnings():
1✔
921
            warnings.filterwarnings("ignore", category=DeprecationWarning, module="mthree.utils")
1✔
922
            circuits = get_circuits_hardware(num_qubits, base_circuit, self.fake_backend)
1✔
923
        
924
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
925
        
926
        # Mock sampler that raises an exception
927
        mock_sampler = MagicMock()
1✔
928
        mock_sampler_class.return_value = mock_sampler
1✔
929
        mock_sampler.run.side_effect = Exception("Job submission failed")
1✔
930
        
931
        with self.assertRaises(Exception):
1✔
932
            get_samples_hardware(
1✔
933
                num_qubits=num_qubits,
934
                shots=1024,
935
                circuits=circuits,
936
                parameters=parameters,
937
                device=self.fake_backend,
938
                error_mitigation=False
939
            )
940

941
    def test_parameter_dimension_mismatch(self):
1✔
942
        """Test functions with parameter dimension mismatches."""
943
        num_qubits = 3
1✔
944
        base_circuit = real_amplitudes(num_qubits, reps=2)
1✔
945
        circuits = get_circuits(num_qubits, base_circuit)
1✔
946
        
947
        # Create parameters with wrong dimensions
948
        wrong_params_1d = np.random.rand(5)  # Wrong count
1✔
949
        wrong_params_2d = np.random.rand(2, 2)  # Wrong shape
1✔
950
        
951
        test_params = [wrong_params_1d, wrong_params_2d]
1✔
952
        
953
        for params in test_params:
1✔
954
            with self.subTest(params_shape=params.shape):
1✔
955
                with self.assertRaises(Exception):
1✔
956
                    get_samples_noisy(
1✔
957
                        num_qubits=num_qubits,
958
                        circuits=circuits,
959
                        shots=1024,
960
                        parameters=params,
961
                        backend_sim=self.backend_sim,
962
                        error_mitigation=False
963
                    )
964

965
    def test_extreme_values(self):
1✔
966
        """Test functions with extreme input values."""
967
        # Test with very large qubit count (should be handled gracefully)
968
        large_qubits = 20
1✔
969
        
970
        # This should either work or fail gracefully, not crash
971
        try:
1✔
972
            simple_circuit = QuantumCircuit(large_qubits)
1✔
973
            circuits = get_circuits(large_qubits, simple_circuit)
1✔
974
            self.assertEqual(len(circuits), large_qubits + 1)
1✔
975
        except (MemoryError, Exception):
×
976
            # Large systems might legitimately fail due to memory constraints
977
            pass
×
978

979
        # Test with very small valid inputs
980
        minimal_circuit = QuantumCircuit(1)
1✔
981
        minimal_circuits = get_circuits(1, minimal_circuit)
1✔
982
        self.assertEqual(len(minimal_circuits), 2)  # Base + 1 H variant
1✔
983

984

985
class TestWarningSuppressionAndEdgeCases(unittest.TestCase):
1✔
986
    """Tests for warning suppression and edge case handling."""
987

988
    def setUp(self):
1✔
989
        """Set up test fixtures."""
990
        self.backend_sim = AerSimulator()
1✔
991
        self.fake_backend = FakeFez()
1✔
992

993
    def test_mthree_warning_suppression_decorator(self):
1✔
994
        """Test that _suppress_mthree_warnings decorator works correctly."""
995
        from hadamard_random_forest.sample import _suppress_mthree_warnings
1✔
996

997
        # Create a function that would generate a deprecation warning
998
        @_suppress_mthree_warnings
1✔
999
        def test_func():
1✔
1000
            import warnings
1✔
1001
            warnings.warn("Test mthree warning", DeprecationWarning)
1✔
1002
            return "success"
1✔
1003

1004
        # Should not raise or print warning
1005
        with warnings.catch_warnings(record=True) as w:
1✔
1006
            warnings.simplefilter("always")
1✔
1007
            result = test_func()
1✔
1008
            self.assertEqual(result, "success")
1✔
1009

1010
    def test_safe_final_measurement_mapping(self):
1✔
1011
        """Test _safe_final_measurement_mapping suppresses mthree warnings."""
1012
        from hadamard_random_forest.sample import _safe_final_measurement_mapping
1✔
1013
        from qiskit import QuantumCircuit
1✔
1014

1015
        # Create a simple circuit
1016
        qc = QuantumCircuit(2)
1✔
1017
        qc.h(0)
1✔
1018
        qc.cx(0, 1)
1✔
1019
        qc.measure_all()
1✔
1020

1021
        # Should not generate warnings
1022
        with warnings.catch_warnings(record=True) as w:
1✔
1023
            warnings.simplefilter("always")
1✔
1024
            mapping = _safe_final_measurement_mapping(qc)
1✔
1025

1026
            # Check that no DeprecationWarnings from mthree.utils were recorded
1027
            mthree_warnings = [warning for warning in w
1✔
1028
                             if issubclass(warning.category, DeprecationWarning)
1029
                             and 'mthree' in str(warning.filename).lower()]
1030
            self.assertEqual(len(mthree_warnings), 0)
1✔
1031

1032
        # Verify mapping is valid (can be list or dict depending on mthree version)
1033
        self.assertIsNotNone(mapping)
1✔
1034

1035
    def test_parameter_none_vs_not_provided(self):
1✔
1036
        """Test explicit None vs not providing parameters argument."""
1037
        num_qubits = 2
1✔
1038
        base_circuit = real_amplitudes(num_qubits)
1✔
1039
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
1040
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1041
        circuits = get_circuits(num_qubits, bound_circuit)
1✔
1042

1043
        # Explicit None
1044
        samples1 = get_samples_noisy(
1✔
1045
            num_qubits=num_qubits,
1046
            circuits=circuits,
1047
            shots=1024,
1048
            parameters=None,
1049
            backend_sim=self.backend_sim,
1050
            error_mitigation=False
1051
        )
1052

1053
        # Not providing parameters (uses default None)
1054
        samples2 = get_samples_noisy(
1✔
1055
            num_qubits=num_qubits,
1056
            circuits=circuits,
1057
            shots=1024,
1058
            backend_sim=self.backend_sim,
1059
            error_mitigation=False
1060
        )
1061

1062
        # Both should succeed
1063
        self.assertEqual(len(samples1), num_qubits + 1)
1✔
1064
        self.assertEqual(len(samples2), num_qubits + 1)
1✔
1065

1066
    def test_parameter_shape_validation(self):
1✔
1067
        """Test various parameter shapes and types."""
1068
        num_qubits = 2
1✔
1069
        base_circuit = real_amplitudes(num_qubits)
1✔
1070
        circuits = get_circuits(num_qubits, base_circuit)
1✔
1071

1072
        # Valid 1D numpy array
1073
        params_valid = np.random.rand(base_circuit.num_parameters)
1✔
1074
        samples_valid = get_samples_noisy(
1✔
1075
            num_qubits=num_qubits,
1076
            circuits=circuits,
1077
            shots=512,
1078
            parameters=params_valid,
1079
            backend_sim=self.backend_sim,
1080
            error_mitigation=False
1081
        )
1082
        self.assertEqual(len(samples_valid), num_qubits + 1)
1✔
1083

1084
        # Python list (should also work)
1085
        params_list = params_valid.tolist()
1✔
1086
        samples_list = get_samples_noisy(
1✔
1087
            num_qubits=num_qubits,
1088
            circuits=circuits,
1089
            shots=512,
1090
            parameters=params_list,
1091
            backend_sim=self.backend_sim,
1092
            error_mitigation=False
1093
        )
1094
        self.assertEqual(len(samples_list), num_qubits + 1)
1✔
1095

1096

1097
class TestSampleIntegration(unittest.TestCase):
1✔
1098
    """Integration tests for the sample module."""
1099

1100
    def setUp(self):
1✔
1101
        """Set up test fixtures."""
1102
        self.backend_sim = AerSimulator()
1✔
1103
        self.fake_backend = FakeFez()
1✔
1104

1105
    def test_integration_basic_workflow(self):
1✔
1106
        """Test the complete basic workflow integration."""
1107
        num_qubits = 2  # Smaller for faster testing
1✔
1108
        num_trees = 3
1✔
1109
        base_circuit = real_amplitudes(num_qubits)
1✔
1110
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
1111
        shots = 1024
1✔
1112
        
1113
        # Complete workflow
1114
        circuits = get_circuits(num_qubits, base_circuit)
1✔
1115
        samples = get_samples_noisy(
1✔
1116
            num_qubits, circuits, shots, parameters, 
1117
            self.backend_sim, error_mitigation=False
1118
        )
1119
        
1120
        with warnings.catch_warnings():
1✔
1121
            warnings.simplefilter("ignore", UserWarning)
1✔
1122
            statevector = get_statevector(num_qubits, num_trees, samples, save_tree=False)
1✔
1123
        
1124
        # Validate end-to-end
1125
        self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
1126
        self.assertTrue(np.all(np.isfinite(statevector)))
1✔
1127
        self.assertAlmostEqual(np.linalg.norm(statevector), 1.0, places=6)
1✔
1128

1129
    def test_integration_with_error_mitigation(self):
1✔
1130
        """Test integration workflow with error mitigation."""
1131
        num_qubits = 2
1✔
1132
        num_trees = 3
1✔
1133
        base_circuit = real_amplitudes(num_qubits)
1✔
1134
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
1135
        shots = 1024
1✔
1136
        
1137
        # Use mock to avoid expensive M3 calibration
1138
        with patch('hadamard_random_forest.sample.M3Mitigation') as mock_m3:
1✔
1139
            mock_mit_instance = MagicMock()
1✔
1140
            mock_m3.return_value = mock_mit_instance
1✔
1141
            mock_quasi = MagicMock()
1✔
1142
            mock_quasi.nearest_probability_distribution.return_value = {
1✔
1143
                '00': 0.25, '01': 0.25, '10': 0.25, '11': 0.25
1144
            }
1145
            mock_mit_instance.apply_correction.return_value = mock_quasi
1✔
1146
            
1147
            with patch('hadamard_random_forest.sample.mthree_utils.final_measurement_mapping') as mock_mapping:
1✔
1148
                mock_mapping.return_value = [0, 1]
1✔
1149
                
1150
                circuits = get_circuits(num_qubits, base_circuit)
1✔
1151
                samples = get_samples_noisy(
1✔
1152
                    num_qubits, circuits, shots, parameters,
1153
                    self.backend_sim, error_mitigation=True
1154
                )
1155
                
1156
                with warnings.catch_warnings():
1✔
1157
                    warnings.simplefilter("ignore", UserWarning)
1✔
1158
                    statevector = get_statevector(num_qubits, num_trees, samples, save_tree=False)
1✔
1159
                
1160
                # Validate workflow with mitigation
1161
                self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
1162
                self.assertTrue(np.all(np.isfinite(statevector)))
1✔
1163

1164
    def test_integration_different_ansatz_types(self):
1✔
1165
        """Test integration with different circuit ansatz types."""
1166
        num_qubits = 2
1✔
1167
        num_trees = 3
1✔
1168
        shots = 1024
1✔
1169
        
1170
        ansatz_types = [
1✔
1171
            real_amplitudes(num_qubits, reps=1),
1172
            efficient_su2(num_qubits, reps=1)
1173
        ]
1174
        
1175
        for i, base_circuit in enumerate(ansatz_types):
1✔
1176
            with self.subTest(ansatz_type=i):
1✔
1177
                parameters = np.random.rand(base_circuit.num_parameters)
1✔
1178
                
1179
                circuits = get_circuits(num_qubits, base_circuit)
1✔
1180
                samples = get_samples_noisy(
1✔
1181
                    num_qubits, circuits, shots, parameters,
1182
                    self.backend_sim, error_mitigation=False
1183
                )
1184
                
1185
                with warnings.catch_warnings():
1✔
1186
                    warnings.simplefilter("ignore", UserWarning)
1✔
1187
                    statevector = get_statevector(num_qubits, num_trees, samples, save_tree=False)
1✔
1188
                
1189
                self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
1190
                self.assertTrue(np.all(np.isfinite(statevector)))
1✔
1191

1192
    @patch('hadamard_random_forest.sample.Sampler')
1✔
1193
    def test_integration_hardware_workflow(self, mock_sampler_class):
1✔
1194
        """Test integration of hardware workflow with mocks."""
1195
        num_qubits = 2
1✔
1196
        num_trees = 3
1✔
1197
        base_circuit = real_amplitudes(num_qubits)
1✔
1198
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
1199
        shots = 1024
1✔
1200
        
1201
        # Mock hardware workflow
1202
        mock_sampler = MagicMock()
1✔
1203
        mock_sampler_class.return_value = mock_sampler
1✔
1204
        
1205
        mock_job = MagicMock()
1✔
1206
        mock_result = MagicMock()
1✔
1207
        mock_data = MagicMock()
1✔
1208
        mock_meas = MagicMock()
1✔
1209
        
1210
        mock_sampler.run.return_value = mock_job
1✔
1211
        mock_job.result.return_value = [mock_result]
1✔
1212
        mock_result.data = mock_data
1✔
1213
        mock_data.meas = mock_meas
1✔
1214
        mock_meas.get_counts.return_value = {'00': 256, '01': 256, '10': 256, '11': 256}
1✔
1215
        mock_job.job_id.return_value = "test_job"
1✔
1216
        mock_job.usage_estimation = {'quantum_seconds': 1.5}
1✔
1217
        
1218
        with patch('hadamard_random_forest.sample.mthree_utils.final_measurement_mapping') as mock_mapping:
1✔
1219
            mock_mapping.return_value = [0, 1]
1✔
1220
            
1221
            # Complete hardware workflow
1222
            circuits = get_circuits_hardware(num_qubits, base_circuit, self.backend_sim)
1✔
1223
            mitigated_samples, raw_samples, job_ids, quantum_times = get_samples_hardware(
1✔
1224
                num_qubits, shots, circuits, parameters,
1225
                self.backend_sim, error_mitigation=False
1226
            )
1227
            
1228
            with warnings.catch_warnings():
1✔
1229
                warnings.simplefilter("ignore", UserWarning)
1✔
1230
                statevector = get_statevector(num_qubits, num_trees, mitigated_samples, save_tree=False)
1✔
1231
            
1232
            # Validate hardware workflow
1233
            self.assertEqual(len(mitigated_samples), num_qubits + 1)
1✔
1234
            self.assertEqual(len(raw_samples), num_qubits + 1)
1✔
1235
            self.assertEqual(len(job_ids), num_qubits + 1)
1✔
1236
            self.assertEqual(len(quantum_times), num_qubits + 1)
1✔
1237
            self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
1238

1239
    def test_performance_scaling(self):
1✔
1240
        """Test performance scaling across different system sizes."""
1241
        import time
1✔
1242
        
1243
        results = {}
1✔
1244
        max_qubits = 4  # Keep reasonable for testing
1✔
1245
        
1246
        for num_qubits in range(2, max_qubits + 1):
1✔
1247
            base_circuit = real_amplitudes(num_qubits, reps=1)
1✔
1248
            parameters = np.random.rand(base_circuit.num_parameters)
1✔
1249
            
1250
            start_time = time.time()
1✔
1251
            
1252
            # Time the circuit generation
1253
            circuits = get_circuits(num_qubits, base_circuit)
1✔
1254
            
1255
            # Time the sampling (with reduced shots for speed)
1256
            samples = get_samples_noisy(
1✔
1257
                num_qubits, circuits, 512, parameters,  # Reduced shots
1258
                self.backend_sim, error_mitigation=False
1259
            )
1260
            
1261
            # Time the reconstruction (with fewer trees)
1262
            with warnings.catch_warnings():
1✔
1263
                warnings.simplefilter("ignore", UserWarning)
1✔
1264
                statevector = get_statevector(num_qubits, 3, samples, save_tree=False)
1✔
1265
            
1266
            elapsed_time = time.time() - start_time
1✔
1267
            results[num_qubits] = elapsed_time
1✔
1268
            
1269
            # Validate result
1270
            self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
1271
            self.assertTrue(np.all(np.isfinite(statevector)))
1✔
1272
        
1273
        # Basic scaling check - should be reasonable
1274
        for num_qubits, time_taken in results.items():
1✔
1275
            self.assertLess(time_taken, 60.0)  # Should complete within 60 seconds
1✔
1276
        
1277
        # Check that scaling is not exponential in the small regime
1278
        if len(results) >= 2:
1✔
1279
            times = list(results.values())
1✔
1280
            # Time shouldn't increase by more than factor of 10 per qubit for small systems
1281
            for i in range(1, len(times)):
1✔
1282
                self.assertLess(times[i] / times[i-1], 10.0)
1✔
1283

1284
    def test_memory_usage_reasonable(self):
1✔
1285
        """Test that memory usage is reasonable for moderate system sizes."""
1286
        import psutil
1✔
1287
        import os
1✔
1288

1289
        process = psutil.Process(os.getpid())
1✔
1290
        initial_memory = process.memory_info().rss / 1024 / 1024  # MB
1✔
1291

1292
        # Test with moderate system size
1293
        num_qubits = 4
1✔
1294
        num_trees = 5
1✔
1295
        base_circuit = real_amplitudes(num_qubits, reps=2)
1✔
1296
        parameters = np.random.rand(base_circuit.num_parameters)
1✔
1297

1298
        circuits = get_circuits(num_qubits, base_circuit)
1✔
1299
        samples = get_samples_noisy(
1✔
1300
            num_qubits, circuits, 1024, parameters,
1301
            self.backend_sim, error_mitigation=False
1302
        )
1303

1304
        with warnings.catch_warnings():
1✔
1305
            warnings.simplefilter("ignore", UserWarning)
1✔
1306
            statevector = get_statevector(num_qubits, num_trees, samples, save_tree=False)
1✔
1307

1308
        final_memory = process.memory_info().rss / 1024 / 1024  # MB
1✔
1309
        memory_increase = final_memory - initial_memory
1✔
1310

1311
        # Memory increase should be reasonable (less than 500MB for 4 qubits)
1312
        self.assertLess(memory_increase, 500.0)
1✔
1313

1314
        # Result should still be valid
1315
        self.assertEqual(statevector.shape, (2**num_qubits,))
1✔
1316
        self.assertTrue(np.all(np.isfinite(statevector)))
1✔
1317

1318
    def test_integration_prebound_vs_parameterized_equivalence(self):
1✔
1319
        """Test that pre-bound and parameterized workflows produce equivalent results."""
1320
        num_qubits = 3
1✔
1321
        num_trees = 7
1✔
1322
        shots = 2048
1✔
1323

1324
        # Create a parameterized circuit
1325
        base_circuit = real_amplitudes(num_qubits, reps=1)
1✔
1326
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
1327

1328
        # Fix random seed for reproducibility
1329
        fix_random_seed(12345)
1✔
1330

1331
        # Workflow 1: Traditional parameterized approach
1332
        circuits_param = get_circuits(num_qubits, base_circuit)
1✔
1333
        samples_param = get_samples_noisy(
1✔
1334
            num_qubits, circuits_param, shots, parameters,
1335
            self.backend_sim, error_mitigation=False
1336
        )
1337

1338
        fix_random_seed(12345)
1✔
1339
        with warnings.catch_warnings():
1✔
1340
            warnings.simplefilter("ignore", UserWarning)
1✔
1341
            statevector_param = get_statevector(num_qubits, num_trees, samples_param, save_tree=False)
1✔
1342

1343
        # Workflow 2: Pre-bound approach
1344
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1345
        circuits_bound = get_circuits(num_qubits, bound_circuit)
1✔
1346

1347
        fix_random_seed(12345)
1✔
1348
        samples_bound = get_samples_noisy(
1✔
1349
            num_qubits, circuits_bound, shots, None,
1350
            self.backend_sim, error_mitigation=False
1351
        )
1352

1353
        fix_random_seed(12345)
1✔
1354
        with warnings.catch_warnings():
1✔
1355
            warnings.simplefilter("ignore", UserWarning)
1✔
1356
            statevector_bound = get_statevector(num_qubits, num_trees, samples_bound, save_tree=False)
1✔
1357

1358
        # Both approaches should produce very similar results
1359
        # Due to shot noise, we expect close but not exact match
1360
        self.assertEqual(statevector_param.shape, statevector_bound.shape)
1✔
1361

1362
        # Check that the statevectors are close (allowing for statistical variation)
1363
        # Use a generous tolerance due to shot noise
1364
        fidelity = np.abs(np.dot(np.conj(statevector_param), statevector_bound))**2
1✔
1365
        self.assertGreater(fidelity, 0.9, "Pre-bound and parameterized approaches should produce similar results")
1✔
1366

1367
        # Both should be properly normalized
1368
        self.assertAlmostEqual(np.linalg.norm(statevector_param), 1.0, places=6)
1✔
1369
        self.assertAlmostEqual(np.linalg.norm(statevector_bound), 1.0, places=6)
1✔
1370

1371
    def test_integration_prebound_full_workflow_with_verification(self):
1✔
1372
        """Test complete pre-bound workflow with result verification."""
1373
        num_qubits = 2
1✔
1374
        num_trees = 11
1✔
1375
        shots = 4096
1✔
1376

1377
        # Create and bind circuit
1378
        base_circuit = real_amplitudes(num_qubits, reps=1)
1✔
1379
        parameters = np.random.rand(base_circuit.num_parameters) * np.pi
1✔
1380
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1381

1382
        # Get the exact statevector for comparison
1383
        from qiskit.quantum_info import Statevector
1✔
1384
        exact_statevector = Statevector(bound_circuit)
1✔
1385

1386
        # Run the HRF reconstruction workflow
1387
        circuits = get_circuits(num_qubits, bound_circuit)
1✔
1388
        samples = get_samples_noisy(
1✔
1389
            num_qubits, circuits, shots, None,
1390
            self.backend_sim, error_mitigation=False
1391
        )
1392

1393
        with warnings.catch_warnings():
1✔
1394
            warnings.simplefilter("ignore", UserWarning)
1✔
1395
            reconstructed = get_statevector(num_qubits, num_trees, samples, save_tree=False)
1✔
1396

1397
        # Verify reconstruction quality
1398
        from qiskit.quantum_info import state_fidelity
1✔
1399
        fidelity = state_fidelity(reconstructed, exact_statevector)
1✔
1400

1401
        # With enough shots and trees, fidelity should be reasonably high
1402
        self.assertGreater(fidelity, 0.8, f"Fidelity {fidelity} is too low")
1✔
1403

1404
        # Verify normalization
1405
        self.assertAlmostEqual(np.linalg.norm(reconstructed), 1.0, places=6)
1✔
1406

1407
        # Verify dimensions
1408
        self.assertEqual(len(reconstructed), 2**num_qubits)
1✔
1409

1410
    def test_get_samples_noisy_without_parameters(self):
1✔
1411
        """Test get_samples_noisy with circuits that have pre-bound parameters."""
1412
        num_qubits = 3
1✔
1413
        shots = 1024
1✔
1414
        
1415
        # Create a parameterized circuit
1416
        base_circuit = real_amplitudes(num_qubits)
1✔
1417
        
1418
        # Bind parameters to the base circuit first
1419
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
1420
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1421
        
1422
        # Create circuits from bound circuit
1423
        circuits = get_circuits(num_qubits, bound_circuit)
1✔
1424
        
1425
        # Call without parameters - should work since circuits are already bound
1426
        samples = get_samples_noisy(
1✔
1427
            num_qubits=num_qubits,
1428
            circuits=circuits,
1429
            shots=shots,
1430
            backend_sim=self.backend_sim,
1431
            error_mitigation=False
1432
        )
1433
        
1434
        # Verify results
1435
        self.assertEqual(len(samples), num_qubits + 1)
1✔
1436
        for sample in samples:
1✔
1437
            self.assertEqual(len(sample), 2**num_qubits)
1✔
1438
            self.assertAlmostEqual(np.sum(sample), 1.0, places=2)
1✔
1439
            self.assertTrue(np.all(sample >= 0))
1✔
1440

1441
    def test_get_samples_noisy_optional_parameters(self):
1✔
1442
        """Test get_samples_noisy with optional parameters."""
1443
        num_qubits = 3
1✔
1444
        shots = 1024
1✔
1445

1446
        # Test 1: Parameterized circuit with explicit parameters
1447
        base_circuit = real_amplitudes(num_qubits)
1✔
1448
        circuits = get_circuits(num_qubits, base_circuit)
1✔
1449
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
1450
        samples_with_params = get_samples_noisy(
1✔
1451
            num_qubits=num_qubits,
1452
            circuits=circuits,
1453
            shots=shots,
1454
            parameters=parameters,
1455
            backend_sim=self.backend_sim,
1456
            error_mitigation=False
1457
        )
1458
        self.assertEqual(len(samples_with_params), num_qubits + 1)
1✔
1459

1460
        # Test 2: Pre-bound circuit with None parameters
1461
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1462
        bound_circuits = get_circuits(num_qubits, bound_circuit)
1✔
1463
        samples_without_params = get_samples_noisy(
1✔
1464
            num_qubits=num_qubits,
1465
            circuits=bound_circuits,
1466
            shots=shots,
1467
            parameters=None,
1468
            backend_sim=self.backend_sim,
1469
            error_mitigation=False
1470
        )
1471
        self.assertEqual(len(samples_without_params), num_qubits + 1)
1✔
1472

1473
        # Test 3: Pre-bound circuit can also work with backend_sim being optional
1474
        samples_no_params_no_backend = get_samples_noisy(
1✔
1475
            num_qubits=num_qubits,
1476
            circuits=bound_circuits,
1477
            shots=shots,
1478
            backend_sim=self.backend_sim,
1479
            error_mitigation=False
1480
        )
1481
        self.assertEqual(len(samples_no_params_no_backend), num_qubits + 1)
1✔
1482

1483

1484
    @patch('hadamard_random_forest.sample.Sampler')
1✔
1485
    def test_get_samples_hardware_without_parameters(self, mock_sampler_class):
1✔
1486
        """Test get_samples_hardware with pre-bound circuits."""
1487
        num_qubits = 2
1✔
1488
        
1489
        # Create a parameterized circuit
1490
        base_circuit = real_amplitudes(num_qubits)
1✔
1491
        
1492
        # Bind parameters first
1493
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
1494
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1495
        circuits = [bound_circuit.copy() for _ in range(3)]
1✔
1496
        
1497
        # Mock the sampler
1498
        mock_sampler = MagicMock()
1✔
1499
        mock_sampler_class.return_value = mock_sampler
1✔
1500
        
1501
        mock_result = MagicMock()
1✔
1502
        mock_result.data.meas.get_counts.return_value = {'00': 500, '11': 500}
1✔
1503
        mock_job = MagicMock()
1✔
1504
        mock_job.result.return_value = [mock_result]
1✔
1505
        mock_job.job_id.return_value = 'test_job_123'
1✔
1506
        mock_job.usage_estimation = {'quantum_seconds': 1.5}
1✔
1507
        mock_sampler.run.return_value = mock_job
1✔
1508
        
1509
        # Run without parameters
1510
        result = get_samples_hardware(
1✔
1511
            num_qubits=num_qubits,
1512
            shots=1000,
1513
            circuits=circuits,
1514
            device=MagicMock(),
1515
            error_mitigation=False
1516
        )
1517
        
1518
        mitigated, raw, job_ids, qtimes = result
1✔
1519
        
1520
        # Verify results
1521
        self.assertEqual(len(mitigated), 3)
1✔
1522
        self.assertEqual(len(raw), 3)
1✔
1523
        self.assertEqual(len(job_ids), 3)
1✔
1524
        
1525
        # Verify sampler was called with circuits only (no parameters)
1526
        for call in mock_sampler.run.call_args_list:
1✔
1527
            # When no parameters, should be called with just the circuit
1528
            self.assertEqual(len(call[0][0]), 1)
1✔
1529

1530
    @patch('hadamard_random_forest.sample.Sampler')
1✔
1531
    @patch('hadamard_random_forest.sample.mthree.M3Mitigation')
1✔
1532
    @patch('hadamard_random_forest.sample.mthree_utils.final_measurement_mapping')
1✔
1533
    def test_get_samples_hardware_prebound_with_mitigation(self, mock_mapping, mock_m3, mock_sampler_class):
1✔
1534
        """Test get_samples_hardware with pre-bound circuits AND error mitigation."""
1535
        num_qubits = 2
1✔
1536
        base_circuit = real_amplitudes(num_qubits)
1✔
1537

1538
        # Bind parameters first
1539
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
1540
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1541

1542
        # Create hardware circuits from pre-bound circuit
1543
        circuits = get_circuits_hardware(num_qubits, bound_circuit, self.fake_backend)
1✔
1544

1545
        # Mock sampler
1546
        mock_sampler = MagicMock()
1✔
1547
        mock_sampler_class.return_value = mock_sampler
1✔
1548

1549
        mock_result = MagicMock()
1✔
1550
        mock_result.data.meas.get_counts.return_value = {'00': 500, '11': 500}
1✔
1551
        mock_job = MagicMock()
1✔
1552
        mock_job.result.return_value = [mock_result]
1✔
1553
        mock_job.job_id.return_value = 'test_job_prebound_mit'
1✔
1554
        mock_job.usage_estimation = {'quantum_seconds': 2.0}
1✔
1555
        mock_sampler.run.return_value = mock_job
1✔
1556

1557
        # Mock M3 mitigation
1558
        mock_mit_instance = MagicMock()
1✔
1559
        mock_m3.return_value = mock_mit_instance
1✔
1560
        mock_quasi = MagicMock()
1✔
1561
        mock_quasi.nearest_probability_distribution.return_value = {
1✔
1562
            '00': 0.5, '11': 0.5
1563
        }
1564
        mock_mit_instance.apply_correction.return_value = mock_quasi
1✔
1565

1566
        # Mock mapping
1567
        mock_mapping.return_value = list(range(num_qubits))
1✔
1568

1569
        # Run without parameters (pre-bound) but WITH error mitigation
1570
        result = get_samples_hardware(
1✔
1571
            num_qubits=num_qubits,
1572
            shots=1000,
1573
            circuits=circuits,
1574
            parameters=None,
1575
            device=self.fake_backend,
1576
            error_mitigation=True
1577
        )
1578

1579
        mitigated, raw, job_ids, qtimes = result
1✔
1580

1581
        # Verify mitigation was used
1582
        self.assertTrue(mock_m3.called)
1✔
1583
        self.assertTrue(mock_mit_instance.apply_correction.called)
1✔
1584

1585
        # Verify results
1586
        self.assertEqual(len(mitigated), num_qubits + 1)
1✔
1587
        self.assertEqual(len(raw), num_qubits + 1)
1✔
1588
        self.assertEqual(len(job_ids), num_qubits + 1)
1✔
1589

1590
        # Verify sampler was called without parameters
1591
        for call in mock_sampler.run.call_args_list:
1✔
1592
            self.assertEqual(len(call[0][0]), 1)
1✔
1593

1594
    def test_prebound_parameter_preservation_through_transpilation(self):
1✔
1595
        """Test that pre-bound parameters are preserved through transpilation."""
1596
        num_qubits = 3
1✔
1597
        base_circuit = real_amplitudes(num_qubits, reps=1)
1✔
1598

1599
        # Bind parameters
1600
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
1601
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1602

1603
        # Verify circuit is bound
1604
        self.assertEqual(bound_circuit.num_parameters, 0)
1✔
1605

1606
        # Create circuits (which transpiles)
1607
        circuits = get_circuits(num_qubits, bound_circuit)
1✔
1608

1609
        # Verify all circuits remain parameter-free
1610
        for circuit in circuits:
1✔
1611
            self.assertEqual(circuit.num_parameters, 0)
1✔
1612

1613
        # These circuits should be executable without additional parameters
1614
        samples = get_samples_noisy(
1✔
1615
            num_qubits=num_qubits,
1616
            circuits=circuits,
1617
            shots=1024,
1618
            parameters=None,
1619
            backend_sim=self.backend_sim,
1620
            error_mitigation=False
1621
        )
1622

1623
        self.assertEqual(len(samples), num_qubits + 1)
1✔
1624
        for sample in samples:
1✔
1625
            self.assertTrue(np.all(sample >= 0))
1✔
1626
            self.assertAlmostEqual(np.sum(sample), 1.0, places=2)
1✔
1627

1628
    def test_mixed_bound_unbound_circuits_error(self):
1✔
1629
        """Test that mixing bound and unbound circuits with wrong parameters raises error."""
1630
        num_qubits = 2
1✔
1631
        base_circuit = real_amplitudes(num_qubits)
1✔
1632

1633
        # Create one bound circuit
1634
        parameters = np.random.rand(base_circuit.num_parameters) * 2 * np.pi
1✔
1635
        bound_circuit = base_circuit.assign_parameters(parameters)
1✔
1636

1637
        # Mix bound and unbound circuits
1638
        circuits = [
1✔
1639
            bound_circuit.measure_all(inplace=False),
1640
            base_circuit.measure_all(inplace=False),
1641
            bound_circuit.measure_all(inplace=False)
1642
        ]
1643

1644
        # Trying to pass parameters to mixed circuits should fail on unbound circuit
1645
        with self.assertRaises(Exception):
1✔
1646
            get_samples_noisy(
1✔
1647
                num_qubits=num_qubits,
1648
                circuits=circuits,
1649
                shots=1024,
1650
                parameters=None,
1651
                backend_sim=self.backend_sim,
1652
                error_mitigation=False
1653
            )
1654

1655
    def test_empty_parameter_array_handling(self):
1✔
1656
        """Test handling of empty parameter arrays."""
1657
        num_qubits = 2
1✔
1658

1659
        # Create a circuit with NO parameters (e.g., just |0> state)
1660
        simple_circuit = QuantumCircuit(num_qubits)
1✔
1661
        circuits = get_circuits(num_qubits, simple_circuit)
1✔
1662

1663
        # Should work with None parameters
1664
        samples1 = get_samples_noisy(
1✔
1665
            num_qubits=num_qubits,
1666
            circuits=circuits,
1667
            shots=1024,
1668
            parameters=None,
1669
            backend_sim=self.backend_sim,
1670
            error_mitigation=False
1671
        )
1672

1673
        # Should also work with empty array
1674
        samples2 = get_samples_noisy(
1✔
1675
            num_qubits=num_qubits,
1676
            circuits=circuits,
1677
            shots=1024,
1678
            parameters=np.array([]),
1679
            backend_sim=self.backend_sim,
1680
            error_mitigation=False
1681
        )
1682

1683
        self.assertEqual(len(samples1), num_qubits + 1)
1✔
1684
        self.assertEqual(len(samples2), num_qubits + 1)
1✔
1685

1686
        # Both should give similar results (measuring |00...0> state)
1687
        for s1, s2 in zip(samples1, samples2):
1✔
1688
            self.assertTrue(np.all(s1 >= 0))
1✔
1689
            self.assertTrue(np.all(s2 >= 0))
1✔
1690

1691
if __name__ == '__main__':
1✔
1692
    unittest.main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc