0
# Quantum Information Utilities
1
2
Quantum information theory tools, Pauli string operations, parametric circuits, register management, program sets, and quantum computing algorithms.
3
4
## Core Imports
5
6
```python { .api }
7
# Quantum information
8
from braket.quantum_information import PauliString
9
10
# Circuit registers and qubits (from circuits module)
11
from braket.circuits import Qubit, QubitInput, QubitSet, QubitSetInput
12
13
# Parametric circuits and free parameters (from circuits module)
14
from braket.circuits import FreeParameter, FreeParameterExpression, Parameterizable
15
16
# Program sets for batch execution
17
from braket.program_sets import CircuitBinding, ParameterSets, ParameterSetsLike, ProgramSet
18
19
# Timing data structures
20
from braket.timings import TimeSeries, TimeSeriesItem
21
```
22
23
## Pauli String Operations
24
25
### PauliString Class
26
27
```python { .api }
28
from braket.quantum_information import PauliString
29
import numpy as np
30
31
class PauliString:
32
"""Representation and manipulation of Pauli strings."""
33
34
def __init__(self, pauli_dict: dict = None):
35
"""
36
Initialize Pauli string from dictionary representation.
37
38
Args:
39
pauli_dict: Dictionary mapping qubit indices to Pauli operators
40
{'X': [0, 2], 'Y': [1], 'Z': [3]} represents X₀Y₁X₂Z₃
41
"""
42
self.pauli_dict = pauli_dict or {}
43
44
@classmethod
45
def from_string(cls, pauli_string: str) -> 'PauliString':
46
"""
47
Create PauliString from string representation.
48
49
Args:
50
pauli_string: String like 'XIYIZ' or 'X0 Y1 Z2'
51
52
Returns:
53
PauliString: Parsed Pauli string
54
"""
55
pass
56
57
def __str__(self) -> str:
58
"""
59
String representation of Pauli string.
60
61
Returns:
62
str: Human-readable Pauli string
63
"""
64
pass
65
66
def __mul__(self, other) -> 'PauliString':
67
"""
68
Multiply two Pauli strings.
69
70
Args:
71
other: Another PauliString or scalar
72
73
Returns:
74
PauliString: Product of Pauli strings
75
"""
76
pass
77
78
def __add__(self, other) -> 'PauliSum':
79
"""
80
Add Pauli strings to create Pauli sum (Hamiltonian).
81
82
Args:
83
other: Another PauliString
84
85
Returns:
86
PauliSum: Sum of Pauli strings
87
"""
88
pass
89
90
def commutes_with(self, other: 'PauliString') -> bool:
91
"""
92
Check if this Pauli string commutes with another.
93
94
Args:
95
other: Another Pauli string
96
97
Returns:
98
bool: True if strings commute
99
"""
100
pass
101
102
def expectation_value(self, state_vector: np.ndarray) -> complex:
103
"""
104
Compute expectation value of Pauli string in given state.
105
106
Args:
107
state_vector: Quantum state vector
108
109
Returns:
110
complex: Expectation value ⟨ψ|P|ψ⟩
111
"""
112
pass
113
114
def to_matrix(self) -> np.ndarray:
115
"""
116
Convert Pauli string to matrix representation.
117
118
Returns:
119
np.ndarray: Matrix representation of Pauli string
120
"""
121
pass
122
123
@property
124
def qubits(self) -> list[int]:
125
"""Get list of qubits involved in Pauli string."""
126
pass
127
128
@property
129
def weight(self) -> int:
130
"""Get weight (number of non-identity Paulis)."""
131
pass
132
133
# Pauli string examples
134
def create_pauli_hamiltonians() -> dict:
135
"""
136
Create common Pauli string Hamiltonians for quantum algorithms.
137
138
Returns:
139
dict: Collection of important Hamiltonians
140
"""
141
hamiltonians = {}
142
143
# Ising model Hamiltonian: H = -J∑ZᵢZⱼ - h∑Xᵢ
144
# 1D chain with 4 qubits
145
ising_terms = []
146
J = 1.0 # Coupling strength
147
h = 0.5 # Transverse field
148
149
# ZZ interactions
150
for i in range(3): # 0-1, 1-2, 2-3 pairs
151
zz_string = PauliString({'Z': [i, i+1]})
152
ising_terms.append((-J, zz_string))
153
154
# X transverse field
155
for i in range(4):
156
x_string = PauliString({'X': [i]})
157
ising_terms.append((-h, x_string))
158
159
hamiltonians['ising_1d'] = ising_terms
160
161
# Heisenberg model: H = J∑(XᵢXⱼ + YᵢYⱼ + ZᵢZⱼ)
162
heisenberg_terms = []
163
J_heis = 1.0
164
165
for i in range(3):
166
# XX interaction
167
xx_string = PauliString({'X': [i, i+1]})
168
heisenberg_terms.append((J_heis, xx_string))
169
170
# YY interaction
171
yy_string = PauliString({'Y': [i, i+1]})
172
heisenberg_terms.append((J_heis, yy_string))
173
174
# ZZ interaction
175
zz_string = PauliString({'Z': [i, i+1]})
176
heisenberg_terms.append((J_heis, zz_string))
177
178
hamiltonians['heisenberg'] = heisenberg_terms
179
180
# H₂ molecule Hamiltonian (simplified)
181
h2_terms = [
182
(-1.0523732, PauliString({})), # Identity term
183
(0.39793742, PauliString({'Z': [0]})), # Z₀
184
(-0.39793742, PauliString({'Z': [1]})), # Z₁
185
(-0.01128010, PauliString({'Z': [0, 1]})), # Z₀Z₁
186
(0.18093119, PauliString({'X': [0, 1]})) # X₀X₁
187
]
188
189
hamiltonians['h2_molecule'] = h2_terms
190
191
return hamiltonians
192
193
def analyze_pauli_string_properties(pauli_string: PauliString) -> dict:
194
"""
195
Analyze mathematical properties of Pauli string.
196
197
Args:
198
pauli_string: Pauli string to analyze
199
200
Returns:
201
dict: Comprehensive property analysis
202
"""
203
analysis = {
204
'basic_properties': {
205
'weight': pauli_string.weight,
206
'qubits': pauli_string.qubits,
207
'qubit_count': len(pauli_string.qubits),
208
'string_representation': str(pauli_string)
209
},
210
'algebraic_properties': {
211
'is_hermitian': True, # All Pauli strings are Hermitian
212
'eigenvalues': [-1, 1], # All Pauli operators have eigenvalues ±1
213
'trace': 0 if pauli_string.weight > 0 else 2**len(pauli_string.qubits)
214
},
215
'computational_complexity': {
216
'matrix_size': 2**(2 * len(pauli_string.qubits)),
217
'measurement_complexity': 'O(1)', # Single measurement setting
218
'classical_simulation': 'Exponential' if len(pauli_string.qubits) > 20 else 'Feasible'
219
}
220
}
221
222
return analysis
223
224
def find_commuting_pauli_groups(pauli_strings: list[PauliString]) -> list[list[PauliString]]:
225
"""
226
Group Pauli strings by commutation relations.
227
228
Args:
229
pauli_strings: List of Pauli strings to group
230
231
Returns:
232
list[list[PauliString]]: Groups of mutually commuting Pauli strings
233
"""
234
groups = []
235
unassigned = pauli_strings.copy()
236
237
while unassigned:
238
# Start new group with first unassigned string
239
current_group = [unassigned.pop(0)]
240
241
# Find all strings that commute with all strings in current group
242
i = 0
243
while i < len(unassigned):
244
string = unassigned[i]
245
246
# Check if string commutes with all in current group
247
if all(string.commutes_with(group_string) for group_string in current_group):
248
current_group.append(unassigned.pop(i))
249
else:
250
i += 1
251
252
groups.append(current_group)
253
254
return groups
255
256
def optimize_pauli_measurement_grouping(pauli_strings: list[PauliString]) -> dict:
257
"""
258
Optimize measurement grouping for Pauli string expectation values.
259
260
Args:
261
pauli_strings: Pauli strings requiring expectation value measurement
262
263
Returns:
264
dict: Optimized measurement strategy
265
"""
266
# Group by commutation relations
267
commuting_groups = find_commuting_pauli_groups(pauli_strings)
268
269
optimization = {
270
'original_measurements': len(pauli_strings),
271
'optimized_measurements': len(commuting_groups),
272
'reduction_factor': len(pauli_strings) / len(commuting_groups),
273
'measurement_groups': [],
274
'measurement_circuits': []
275
}
276
277
for i, group in enumerate(commuting_groups):
278
group_info = {
279
'group_id': i,
280
'pauli_strings': [str(ps) for ps in group],
281
'measurement_basis': determine_measurement_basis(group),
282
'simultaneous_measurements': len(group)
283
}
284
optimization['measurement_groups'].append(group_info)
285
286
# Create measurement circuit for this group
287
measurement_circuit = create_pauli_measurement_circuit(group)
288
optimization['measurement_circuits'].append(measurement_circuit)
289
290
return optimization
291
292
def determine_measurement_basis(pauli_group: list[PauliString]) -> dict:
293
"""
294
Determine optimal measurement basis for commuting Pauli group.
295
296
Args:
297
pauli_group: Group of commuting Pauli strings
298
299
Returns:
300
dict: Measurement basis transformations needed
301
"""
302
# Analyze required basis rotations for each qubit
303
qubit_basis = {}
304
305
for pauli_string in pauli_group:
306
for pauli_op, qubits in pauli_string.pauli_dict.items():
307
for qubit in qubits:
308
if qubit not in qubit_basis:
309
qubit_basis[qubit] = pauli_op
310
elif qubit_basis[qubit] != pauli_op:
311
# Conflict - this should not happen in commuting group
312
raise ValueError(f"Non-commuting Pauli operations on qubit {qubit}")
313
314
return qubit_basis
315
316
def create_pauli_measurement_circuit(pauli_group: list[PauliString]):
317
"""
318
Create quantum circuit for measuring commuting Pauli group.
319
320
Args:
321
pauli_group: Commuting Pauli strings to measure
322
323
Returns:
324
Circuit: Quantum circuit with appropriate basis rotations
325
"""
326
from braket.circuits import Circuit
327
from braket.circuits.gates import H, S, Si
328
329
circuit = Circuit()
330
measurement_basis = determine_measurement_basis(pauli_group)
331
332
# Add basis rotation gates
333
for qubit, pauli_op in measurement_basis.items():
334
if pauli_op == 'X':
335
circuit.h(qubit) # Rotate to X basis
336
elif pauli_op == 'Y':
337
circuit.si(qubit) # S† gate
338
circuit.h(qubit) # Then Hadamard for Y basis
339
# Z basis needs no rotation
340
341
# Add measurements
342
measured_qubits = list(measurement_basis.keys())
343
circuit.measure(measured_qubits)
344
345
return circuit
346
```
347
348
## Quantum Registers
349
350
### Qubit and QubitSet Management
351
352
```python { .api }
353
from braket.circuits import Qubit, QubitInput, QubitSet, QubitSetInput
354
355
class Qubit:
356
"""Individual qubit representation."""
357
358
def __init__(self, index: int):
359
"""
360
Initialize qubit with index.
361
362
Args:
363
index: Qubit index identifier
364
"""
365
self.index = index
366
367
def __eq__(self, other) -> bool:
368
"""Check equality with another qubit."""
369
return isinstance(other, Qubit) and self.index == other.index
370
371
def __hash__(self) -> int:
372
"""Hash for use in sets and dictionaries."""
373
return hash(self.index)
374
375
def __str__(self) -> str:
376
"""String representation."""
377
return f"Qubit({self.index})"
378
379
class QubitSet:
380
"""Set of qubits with set operations."""
381
382
def __init__(self, qubits: QubitSetInput = None):
383
"""
384
Initialize qubit set.
385
386
Args:
387
qubits: Initial qubits (int, Qubit, list, or range)
388
"""
389
self._qubits = set()
390
if qubits is not None:
391
self.add(qubits)
392
393
def add(self, qubits: QubitSetInput) -> 'QubitSet':
394
"""
395
Add qubits to set.
396
397
Args:
398
qubits: Qubits to add
399
400
Returns:
401
QubitSet: Self for method chaining
402
"""
403
pass
404
405
def union(self, other: 'QubitSet') -> 'QubitSet':
406
"""
407
Union with another qubit set.
408
409
Args:
410
other: Another qubit set
411
412
Returns:
413
QubitSet: Union of qubit sets
414
"""
415
pass
416
417
def intersection(self, other: 'QubitSet') -> 'QubitSet':
418
"""
419
Intersection with another qubit set.
420
421
Args:
422
other: Another qubit set
423
424
Returns:
425
QubitSet: Intersection of qubit sets
426
"""
427
pass
428
429
def __len__(self) -> int:
430
"""Number of qubits in set."""
431
return len(self._qubits)
432
433
def __iter__(self):
434
"""Iterate over qubits in set."""
435
return iter(sorted(self._qubits, key=lambda q: q.index))
436
437
def __contains__(self, qubit: QubitInput) -> bool:
438
"""Check if qubit is in set."""
439
pass
440
441
# Register management examples
442
def create_quantum_register_layout(n_qubits: int, connectivity: str = 'all_to_all') -> dict:
443
"""
444
Create quantum register layout with connectivity constraints.
445
446
Args:
447
n_qubits: Number of qubits in register
448
connectivity: Connectivity type ('all_to_all', 'linear', 'grid')
449
450
Returns:
451
dict: Register layout with connectivity information
452
"""
453
qubits = QubitSet(range(n_qubits))
454
455
layout = {
456
'qubits': list(qubits),
457
'qubit_count': len(qubits),
458
'connectivity_type': connectivity,
459
'adjacency_list': {},
460
'coupling_map': []
461
}
462
463
# Generate connectivity based on type
464
if connectivity == 'all_to_all':
465
for i in range(n_qubits):
466
layout['adjacency_list'][i] = list(range(n_qubits))
467
layout['adjacency_list'][i].remove(i) # Remove self
468
469
for j in range(i+1, n_qubits):
470
layout['coupling_map'].append((i, j))
471
472
elif connectivity == 'linear':
473
for i in range(n_qubits):
474
neighbors = []
475
if i > 0:
476
neighbors.append(i-1)
477
if i < n_qubits - 1:
478
neighbors.append(i+1)
479
layout['adjacency_list'][i] = neighbors
480
481
if i < n_qubits - 1:
482
layout['coupling_map'].append((i, i+1))
483
484
elif connectivity == 'grid':
485
# Assume square grid for simplicity
486
grid_size = int(n_qubits ** 0.5)
487
if grid_size * grid_size != n_qubits:
488
raise ValueError(f"Grid connectivity requires perfect square number of qubits, got {n_qubits}")
489
490
for i in range(n_qubits):
491
row = i // grid_size
492
col = i % grid_size
493
neighbors = []
494
495
# Add neighbors (up, down, left, right)
496
if row > 0: # Up
497
neighbors.append((row-1) * grid_size + col)
498
if row < grid_size - 1: # Down
499
neighbors.append((row+1) * grid_size + col)
500
if col > 0: # Left
501
neighbors.append(row * grid_size + (col-1))
502
if col < grid_size - 1: # Right
503
neighbors.append(row * grid_size + (col+1))
504
505
layout['adjacency_list'][i] = neighbors
506
507
# Add coupling map entries
508
for neighbor in neighbors:
509
if i < neighbor: # Avoid duplicates
510
layout['coupling_map'].append((i, neighbor))
511
512
return layout
513
514
def optimize_qubit_allocation(circuit, device_layout: dict) -> dict:
515
"""
516
Optimize qubit allocation for circuit on device with limited connectivity.
517
518
Args:
519
circuit: Quantum circuit requiring qubit allocation
520
device_layout: Device connectivity layout
521
522
Returns:
523
dict: Optimized qubit mapping and routing information
524
"""
525
# Extract two-qubit gates from circuit
526
two_qubit_operations = []
527
for instruction in circuit.instructions:
528
if len(instruction.target) == 2:
529
two_qubit_operations.append(tuple(instruction.target))
530
531
# Build circuit connectivity graph
532
circuit_connectivity = {}
533
for qubit in range(circuit.qubit_count):
534
circuit_connectivity[qubit] = []
535
536
for q1, q2 in two_qubit_operations:
537
if q2 not in circuit_connectivity[q1]:
538
circuit_connectivity[q1].append(q2)
539
if q1 not in circuit_connectivity[q2]:
540
circuit_connectivity[q2].append(q1)
541
542
# Simple greedy mapping (more sophisticated algorithms exist)
543
qubit_mapping = {}
544
used_physical_qubits = set()
545
546
# Start with most connected circuit qubit
547
start_qubit = max(circuit_connectivity.keys(), key=lambda q: len(circuit_connectivity[q]))
548
549
# Map to most connected device qubit
550
start_physical = max(device_layout['adjacency_list'].keys(),
551
key=lambda q: len(device_layout['adjacency_list'][q]))
552
553
qubit_mapping[start_qubit] = start_physical
554
used_physical_qubits.add(start_physical)
555
556
# Iteratively map remaining qubits
557
remaining_circuit_qubits = set(range(circuit.qubit_count)) - {start_qubit}
558
559
while remaining_circuit_qubits:
560
best_score = -1
561
best_mapping = None
562
563
for circuit_qubit in remaining_circuit_qubits:
564
for physical_qubit in device_layout['adjacency_list'].keys():
565
if physical_qubit in used_physical_qubits:
566
continue
567
568
# Score based on adjacency to already mapped qubits
569
score = 0
570
for neighbor in circuit_connectivity[circuit_qubit]:
571
if neighbor in qubit_mapping:
572
mapped_neighbor = qubit_mapping[neighbor]
573
if physical_qubit in device_layout['adjacency_list'][mapped_neighbor]:
574
score += 1
575
576
if score > best_score:
577
best_score = score
578
best_mapping = (circuit_qubit, physical_qubit)
579
580
if best_mapping:
581
circuit_qubit, physical_qubit = best_mapping
582
qubit_mapping[circuit_qubit] = physical_qubit
583
used_physical_qubits.add(physical_qubit)
584
remaining_circuit_qubits.remove(circuit_qubit)
585
else:
586
# Fallback: use any available physical qubit
587
circuit_qubit = remaining_circuit_qubits.pop()
588
for physical_qubit in device_layout['adjacency_list'].keys():
589
if physical_qubit not in used_physical_qubits:
590
qubit_mapping[circuit_qubit] = physical_qubit
591
used_physical_qubits.add(physical_qubit)
592
break
593
594
# Calculate routing overhead
595
swap_gates_needed = 0
596
for q1, q2 in two_qubit_operations:
597
p1, p2 = qubit_mapping[q1], qubit_mapping[q2]
598
if p2 not in device_layout['adjacency_list'][p1]:
599
swap_gates_needed += 1 # Simplified - actual routing more complex
600
601
return {
602
'qubit_mapping': qubit_mapping,
603
'routing_overhead': swap_gates_needed,
604
'mapping_efficiency': (len(two_qubit_operations) - swap_gates_needed) / max(len(two_qubit_operations), 1),
605
'physical_qubits_used': len(used_physical_qubits)
606
}
607
```
608
609
## Parametric Computation
610
611
### Free Parameters and Expressions
612
613
```python { .api }
614
from braket.circuits import FreeParameter, FreeParameterExpression, Parameterizable
615
616
class FreeParameter(Parameterizable):
617
"""Free parameter for parameterized quantum circuits."""
618
619
def __init__(self, name: str):
620
"""
621
Initialize free parameter.
622
623
Args:
624
name: Parameter name identifier
625
"""
626
self.name = name
627
628
def __str__(self) -> str:
629
"""String representation."""
630
return self.name
631
632
def __add__(self, other) -> 'FreeParameterExpression':
633
"""Addition with another parameter or number."""
634
return FreeParameterExpression(f"({self.name} + {other})")
635
636
def __sub__(self, other) -> 'FreeParameterExpression':
637
"""Subtraction with another parameter or number."""
638
return FreeParameterExpression(f"({self.name} - {other})")
639
640
def __mul__(self, other) -> 'FreeParameterExpression':
641
"""Multiplication with another parameter or number."""
642
return FreeParameterExpression(f"({self.name} * {other})")
643
644
def __truediv__(self, other) -> 'FreeParameterExpression':
645
"""Division with another parameter or number."""
646
return FreeParameterExpression(f"({self.name} / {other})")
647
648
class FreeParameterExpression(Parameterizable):
649
"""Mathematical expressions with free parameters."""
650
651
def __init__(self, expression: str):
652
"""
653
Initialize parameter expression.
654
655
Args:
656
expression: Mathematical expression string
657
"""
658
self.expression = expression
659
660
def substitute(self, substitutions: dict) -> float:
661
"""
662
Substitute parameter values and evaluate expression.
663
664
Args:
665
substitutions: Dictionary mapping parameter names to values
666
667
Returns:
668
float: Evaluated expression value
669
"""
670
pass
671
672
def get_free_parameters(self) -> set[str]:
673
"""
674
Get all free parameters in expression.
675
676
Returns:
677
set[str]: Set of parameter names
678
"""
679
pass
680
681
def __add__(self, other) -> 'FreeParameterExpression':
682
"""Addition with another expression or number."""
683
return FreeParameterExpression(f"({self.expression} + {other})")
684
685
def __mul__(self, other) -> 'FreeParameterExpression':
686
"""Multiplication with another expression or number."""
687
return FreeParameterExpression(f"({self.expression} * {other})")
688
689
# Parametric computation examples
690
def create_parametric_ansatz(n_qubits: int, n_layers: int) -> dict:
691
"""
692
Create parametric quantum ansatz with systematic parameter naming.
693
694
Args:
695
n_qubits: Number of qubits in ansatz
696
n_layers: Number of parametric layers
697
698
Returns:
699
dict: Parametric ansatz information and parameter structure
700
"""
701
from braket.circuits import Circuit
702
from braket.circuits.gates import Ry, Rz, CNot
703
704
circuit = Circuit()
705
parameters = {}
706
parameter_count = 0
707
708
# Initialize with Hadamard gates
709
for qubit in range(n_qubits):
710
circuit.h(qubit)
711
712
# Parametric layers
713
for layer in range(n_layers):
714
# Single-qubit rotations
715
for qubit in range(n_qubits):
716
# Y rotation parameter
717
theta_param = FreeParameter(f"theta_{layer}_{qubit}")
718
parameters[f"theta_{layer}_{qubit}"] = theta_param
719
circuit.ry(qubit, theta_param)
720
parameter_count += 1
721
722
# Z rotation parameter
723
phi_param = FreeParameter(f"phi_{layer}_{qubit}")
724
parameters[f"phi_{layer}_{qubit}"] = phi_param
725
circuit.rz(qubit, phi_param)
726
parameter_count += 1
727
728
# Entangling gates
729
for qubit in range(n_qubits - 1):
730
circuit.cnot(qubit, (qubit + 1) % n_qubits)
731
732
ansatz_info = {
733
'circuit': circuit,
734
'parameters': parameters,
735
'parameter_count': parameter_count,
736
'parameter_names': list(parameters.keys()),
737
'layers': n_layers,
738
'qubits': n_qubits,
739
'structure': 'Hardware-efficient ansatz'
740
}
741
742
return ansatz_info
743
744
def optimize_parameter_expressions(expressions: list[FreeParameterExpression]) -> dict:
745
"""
746
Optimize parameter expressions for efficient evaluation.
747
748
Args:
749
expressions: List of parameter expressions to optimize
750
751
Returns:
752
dict: Optimization analysis and recommendations
753
"""
754
optimization = {
755
'original_expressions': [expr.expression for expr in expressions],
756
'common_subexpressions': {},
757
'parameter_dependencies': {},
758
'evaluation_order': [],
759
'computational_complexity': {}
760
}
761
762
# Find common subexpressions
763
subexpr_count = {}
764
for expr in expressions:
765
# Simplified parsing - would need proper expression tree analysis
766
subexprs = expr.expression.split()
767
for subexpr in subexprs:
768
if subexpr.startswith('(') and ')' in subexpr:
769
subexpr_count[subexpr] = subexpr_count.get(subexpr, 0) + 1
770
771
# Identify reusable subexpressions
772
optimization['common_subexpressions'] = {
773
subexpr: count for subexpr, count in subexpr_count.items() if count > 1
774
}
775
776
# Analyze parameter dependencies
777
all_parameters = set()
778
for expr in expressions:
779
params = expr.get_free_parameters()
780
all_parameters.update(params)
781
optimization['parameter_dependencies'][expr.expression] = list(params)
782
783
optimization['total_unique_parameters'] = len(all_parameters)
784
785
return optimization
786
787
def generate_parameter_sweep_ranges(ansatz_info: dict, sweep_type: str = 'random') -> dict:
788
"""
789
Generate parameter value ranges for ansatz optimization.
790
791
Args:
792
ansatz_info: Ansatz information from create_parametric_ansatz
793
sweep_type: Type of parameter sweep ('random', 'grid', 'adaptive')
794
795
Returns:
796
dict: Parameter sweep configuration
797
"""
798
import numpy as np
799
800
parameter_names = ansatz_info['parameter_names']
801
n_params = len(parameter_names)
802
803
sweep_config = {
804
'sweep_type': sweep_type,
805
'parameter_count': n_params,
806
'parameter_ranges': {},
807
'suggested_values': {},
808
'optimization_hints': []
809
}
810
811
if sweep_type == 'random':
812
# Random sampling in [0, 2π] for rotation angles
813
for param_name in parameter_names:
814
sweep_config['parameter_ranges'][param_name] = (0, 2*np.pi)
815
sweep_config['suggested_values'][param_name] = np.random.random(10) * 2 * np.pi
816
817
sweep_config['optimization_hints'] = [
818
"Use random search for initial exploration",
819
"Consider Bayesian optimization for refinement",
820
f"Total parameter space size: {n_params} dimensions"
821
]
822
823
elif sweep_type == 'grid':
824
# Grid search with reasonable resolution
825
points_per_dim = max(3, int(10**(1/max(n_params, 1)))) # Adaptive grid resolution
826
827
for param_name in parameter_names:
828
sweep_config['parameter_ranges'][param_name] = (0, 2*np.pi)
829
sweep_config['suggested_values'][param_name] = np.linspace(0, 2*np.pi, points_per_dim)
830
831
total_evaluations = points_per_dim ** n_params
832
sweep_config['optimization_hints'] = [
833
f"Grid search with {points_per_dim} points per parameter",
834
f"Total evaluations needed: {total_evaluations}",
835
"Consider reducing parameters if grid is too large"
836
]
837
838
elif sweep_type == 'adaptive':
839
# Adaptive ranges based on parameter sensitivity
840
for param_name in parameter_names:
841
if 'theta' in param_name: # Y rotations - typically more sensitive
842
sweep_config['parameter_ranges'][param_name] = (0, np.pi)
843
else: # Z rotations - full range
844
sweep_config['parameter_ranges'][param_name] = (0, 2*np.pi)
845
846
# Initial sampling points
847
sweep_config['suggested_values'][param_name] = np.random.random(5) * sweep_config['parameter_ranges'][param_name][1]
848
849
sweep_config['optimization_hints'] = [
850
"Adaptive sampling based on parameter types",
851
"Y rotations limited to [0, π] for efficiency",
852
"Use gradient-based methods when possible"
853
]
854
855
return sweep_config
856
```
857
858
## Time Series and Program Sets
859
860
### Time Series Data Management
861
862
```python { .api }
863
from braket.timings import TimeSeries, TimeSeriesItem
864
865
class TimeSeriesItem:
866
"""Individual time series data item."""
867
868
def __init__(self, time: float, value: float):
869
"""
870
Initialize time series item.
871
872
Args:
873
time: Time point
874
value: Value at time point
875
"""
876
self.time = time
877
self.value = value
878
879
class TimeSeries:
880
"""Time series data representation."""
881
882
def __init__(self, values: list[TimeSeriesItem] = None):
883
"""
884
Initialize time series.
885
886
Args:
887
values: List of time series items
888
"""
889
self.values = values or []
890
891
def add_point(self, time: float, value: float) -> 'TimeSeries':
892
"""
893
Add data point to time series.
894
895
Args:
896
time: Time coordinate
897
value: Value at time
898
899
Returns:
900
TimeSeries: Self for method chaining
901
"""
902
self.values.append(TimeSeriesItem(time, value))
903
return self
904
905
def interpolate(self, time: float) -> float:
906
"""
907
Interpolate value at given time.
908
909
Args:
910
time: Time point for interpolation
911
912
Returns:
913
float: Interpolated value
914
"""
915
pass
916
917
def resample(self, new_times: list[float]) -> 'TimeSeries':
918
"""
919
Resample time series at new time points.
920
921
Args:
922
new_times: New time sampling points
923
924
Returns:
925
TimeSeries: Resampled time series
926
"""
927
pass
928
929
### Program Set Management
930
931
```python { .api }
932
from braket.program_sets import CircuitBinding, ParameterSets, ProgramSet
933
934
class CircuitBinding:
935
"""Circuit parameter binding for program sets."""
936
937
def __init__(self, circuit, parameter_values: dict):
938
"""
939
Initialize circuit binding.
940
941
Args:
942
circuit: Parameterized circuit
943
parameter_values: Parameter value assignments
944
"""
945
self.circuit = circuit
946
self.parameter_values = parameter_values
947
948
def bind_parameters(self):
949
"""
950
Create bound circuit with parameter values substituted.
951
952
Returns:
953
Circuit: Circuit with parameters bound
954
"""
955
pass
956
957
class ParameterSets:
958
"""Parameter set management for optimization."""
959
960
def __init__(self, parameter_names: list[str]):
961
"""
962
Initialize parameter sets.
963
964
Args:
965
parameter_names: Names of parameters to manage
966
"""
967
self.parameter_names = parameter_names
968
self.parameter_sets = []
969
970
def add_parameter_set(self, values: list[float]) -> 'ParameterSets':
971
"""
972
Add parameter value set.
973
974
Args:
975
values: Parameter values in order of parameter_names
976
977
Returns:
978
ParameterSets: Self for method chaining
979
"""
980
if len(values) != len(self.parameter_names):
981
raise ValueError(f"Expected {len(self.parameter_names)} values, got {len(values)}")
982
983
param_dict = dict(zip(self.parameter_names, values))
984
self.parameter_sets.append(param_dict)
985
return self
986
987
def generate_grid(self, ranges: dict, resolution: int = 10) -> 'ParameterSets':
988
"""
989
Generate grid of parameter values.
990
991
Args:
992
ranges: Parameter ranges {param_name: (min, max)}
993
resolution: Grid resolution per parameter
994
995
Returns:
996
ParameterSets: Self with grid parameter sets
997
"""
998
import itertools
999
import numpy as np
1000
1001
grid_values = []
1002
for param_name in self.parameter_names:
1003
if param_name in ranges:
1004
min_val, max_val = ranges[param_name]
1005
param_grid = np.linspace(min_val, max_val, resolution)
1006
grid_values.append(param_grid)
1007
else:
1008
grid_values.append([0.0]) # Default value
1009
1010
# Generate all combinations
1011
for combination in itertools.product(*grid_values):
1012
self.add_parameter_set(list(combination))
1013
1014
return self
1015
1016
class ProgramSet:
1017
"""Collection of parameterized programs for batch execution."""
1018
1019
def __init__(self, base_program, parameter_sets: ParameterSets):
1020
"""
1021
Initialize program set.
1022
1023
Args:
1024
base_program: Base parameterized program
1025
parameter_sets: Parameter sets for batch execution
1026
"""
1027
self.base_program = base_program
1028
self.parameter_sets = parameter_sets
1029
1030
def create_bindings(self) -> list[CircuitBinding]:
1031
"""
1032
Create circuit bindings for all parameter sets.
1033
1034
Returns:
1035
list[CircuitBinding]: Bound circuits for execution
1036
"""
1037
bindings = []
1038
for param_set in self.parameter_sets.parameter_sets:
1039
binding = CircuitBinding(self.base_program, param_set)
1040
bindings.append(binding)
1041
return bindings
1042
1043
def execute_all(self, device, shots: int = 1000) -> list:
1044
"""
1045
Execute all programs in set on device.
1046
1047
Args:
1048
device: Quantum device for execution
1049
shots: Number of shots per program
1050
1051
Returns:
1052
list: Results from all program executions
1053
"""
1054
bindings = self.create_bindings()
1055
bound_circuits = [binding.bind_parameters() for binding in bindings]
1056
1057
# Execute batch
1058
batch_task = device.run_batch(bound_circuits, shots=shots)
1059
return batch_task.results()
1060
1061
# Program set example
1062
def create_optimization_program_set(ansatz_info: dict, optimization_config: dict) -> ProgramSet:
1063
"""
1064
Create program set for variational quantum optimization.
1065
1066
Args:
1067
ansatz_info: Ansatz circuit information
1068
optimization_config: Optimization configuration
1069
1070
Returns:
1071
ProgramSet: Program set for batch optimization
1072
"""
1073
base_circuit = ansatz_info['circuit']
1074
parameter_names = ansatz_info['parameter_names']
1075
1076
# Create parameter sets based on optimization strategy
1077
parameter_sets = ParameterSets(parameter_names)
1078
1079
if optimization_config.get('method') == 'grid_search':
1080
ranges = optimization_config.get('parameter_ranges', {})
1081
resolution = optimization_config.get('resolution', 5)
1082
parameter_sets.generate_grid(ranges, resolution)
1083
1084
elif optimization_config.get('method') == 'random_search':
1085
import numpy as np
1086
n_samples = optimization_config.get('n_samples', 100)
1087
1088
for _ in range(n_samples):
1089
random_values = []
1090
for param_name in parameter_names:
1091
if param_name in optimization_config.get('parameter_ranges', {}):
1092
min_val, max_val = optimization_config['parameter_ranges'][param_name]
1093
random_val = np.random.uniform(min_val, max_val)
1094
else:
1095
random_val = np.random.uniform(0, 2*np.pi)
1096
random_values.append(random_val)
1097
1098
parameter_sets.add_parameter_set(random_values)
1099
1100
return ProgramSet(base_circuit, parameter_sets)
1101
```
1102
1103
This comprehensive quantum information documentation covers Pauli string operations, quantum registers, parametric computation, time series management, and program set utilities provided by the Amazon Braket SDK.