-
Notifications
You must be signed in to change notification settings - Fork 55
/
lindbladerrorgen.py
1770 lines (1529 loc) · 86 KB
/
lindbladerrorgen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
The LindbladErrorgen class and supporting functionality.
"""
#***************************************************************************************************
# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
# Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights
# in this software.
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
#***************************************************************************************************
import warnings as _warnings
import collections as _collections
import copy as _copy
import itertools as _itertools
import numpy as _np
import scipy.linalg as _spl
import scipy.sparse as _sps
import scipy.sparse.linalg as _spsl
from pygsti.baseobjs.opcalc import compact_deriv as _compact_deriv, \
bulk_eval_compact_polynomials_complex as _bulk_eval_compact_polynomials_complex, \
abs_sum_bulk_eval_compact_polynomials_complex as _abs_sum_bulk_eval_compact_polynomials_complex
from pygsti.modelmembers.operations.linearop import LinearOperator as _LinearOperator
from pygsti.modelmembers.operations.lindbladcoefficients import LindbladCoefficientBlock as _LindbladCoefficientBlock
from pygsti.modelmembers import term as _term
from pygsti.evotypes import Evotype as _Evotype
from pygsti.baseobjs import statespace as _statespace
from pygsti.baseobjs.basis import Basis as _Basis, BuiltinBasis as _BuiltinBasis
from pygsti.baseobjs.polynomial import Polynomial as _Polynomial
from pygsti.baseobjs.nicelyserializable import NicelySerializable as _NicelySerializable
from pygsti.baseobjs.errorgenlabel import LocalElementaryErrorgenLabel as _LocalElementaryErrorgenLabel
from pygsti.baseobjs.errorgenlabel import GlobalElementaryErrorgenLabel as _GlobalElementaryErrorgenLabel
from pygsti.tools import basistools as _bt
from pygsti.tools import matrixtools as _mt
from pygsti.tools import optools as _ot
IMAG_TOL = 1e-7 # tolerance for imaginary part being considered zero
class LindbladErrorgen(_LinearOperator):
"""
An Lindblad-form error generator.
This error generator consisting of terms that, with appropriate constraints
ensurse that the resulting (after exponentiation) operation/layer operation
is CPTP. These terms can be divided into "Hamiltonian"-type terms, which
map rho -> i[H,rho] and "non-Hamiltonian"/"other"-type terms, which map rho
-> A rho B + 0.5*(ABrho + rhoAB).
Parameters
----------
dim : int
The Hilbert-Schmidt (superoperator) dimension, which will be the
dimension of the created operator.
lindblad_term_dict : dict
A dictionary specifying which Linblad terms are present in the
parameteriztion. Keys are `(termType, basisLabel1, <basisLabel2>)`
tuples, where `termType` can be `"H"` (Hamiltonian), `"S"`
(Stochastic), or `"A"` (Affine). Hamiltonian and Affine terms always
have a single basis label (so key is a 2-tuple) whereas Stochastic
tuples with 1 basis label indicate a *diagonal* term, and are the
only types of terms allowed when `nonham_mode != "all"`. Otherwise,
Stochastic term tuples can include 2 basis labels to specify
"off-diagonal" non-Hamiltonian Lindblad terms. Basis labels can be
strings or integers. Values are complex coefficients.
basis : Basis, optional
A basis mapping the labels used in the keys of `lindblad_term_dict` to
basis matrices (e.g. numpy arrays or Scipy sparse matrices).
param_mode : {"unconstrained", "cptp", "depol", "reldepol"}
Describes how the Lindblad coefficients/projections relate to the
error generator's parameter values. Allowed values are:
`"unconstrained"` (coeffs are independent unconstrained parameters),
`"cptp"` (independent parameters but constrained so map is CPTP),
`"reldepol"` (all non-Ham. diagonal coeffs take the *same* value),
`"depol"` (same as `"reldepol"` but coeffs must be *positive*)
nonham_mode : {"diagonal", "diag_affine", "all"}
Which non-Hamiltonian Lindblad projections are potentially non-zero.
Allowed values are: `"diagonal"` (only the diagonal Lind. coeffs.),
`"diag_affine"` (diagonal coefficients + affine projections), and
`"all"` (the entire matrix of coefficients is allowed).
truncate : bool, optional
Whether to truncate the projections onto the Lindblad terms in
order to meet constraints (e.g. to preserve CPTP) when necessary.
If False, then an error is thrown when the given dictionary of
Lindblad terms doesn't conform to the constrains.
mx_basis : {'std', 'gm', 'pp', 'qt'} or Basis object
The basis for this error generator's linear mapping. Allowed
values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp),
and Qutrit (qt) (or a custom basis object).
evotype : {"densitymx","svterm","cterm"}
The evolution type of the error generator being constructed.
`"densitymx"` means the usual Lioville density-matrix-vector
propagation via matrix-vector products. `"svterm"` denotes
state-vector term-based evolution (action of operation is obtained by
evaluating the rank-1 terms up to some order). `"cterm"` is similar
but uses Clifford operation action on stabilizer states.
"""
_generators_cache = {} # a custom cache for _init_generators method calls
@classmethod
def from_operation_matrix_and_blocks(cls, op_matrix, lindblad_coefficient_blocks, lindblad_basis='auto',
mx_basis='pp', truncate=True, evotype="default", state_space=None):
sparseOp = _sps.issparse(op_matrix)
#Init base from error generator: sets basis members and ultimately
# the parameters in self.paramvals
if sparseOp:
#Instead of making error_generator(...) compatible with sparse matrices
# we require sparse matrices to have trivial initial error generators
# or we convert to dense:
if _mt.safe_norm(op_matrix - _sps.identity(op_matrix.shape[0], 'd')) < 1e-8:
errgenMx = _sps.csr_matrix(op_matrix.shape, dtype='d') # all zeros
else:
errgenMx = _sps.csr_matrix(
_ot.error_generator(op_matrix.toarray(), _np.identity(op_matrix.shape[0], 'd'),
mx_basis, "logGTi"), dtype='d')
else:
errgenMx = _ot.error_generator(op_matrix, _np.identity(op_matrix.shape[0], 'd'),
mx_basis, "logGTi")
for blk in lindblad_coefficient_blocks:
blk.set_from_errorgen_projections(errgenMx, mx_basis, truncate=truncate)
return cls(lindblad_coefficient_blocks, lindblad_basis, mx_basis, evotype, state_space)
@classmethod
def from_operation_matrix(cls, op_matrix, parameterization='CPTP', lindblad_basis='PP',
mx_basis='pp', truncate=True, evotype="default", state_space=None):
"""
Creates a Lindblad-parameterized error generator from an operation.
Here "operation" means the exponentiated error generator, so this method
essentially takes the matrix log of `op_matrix` and constructs an error
generator from this using :method:`from_error_generator`.
Parameters
----------
op_matrix : numpy array or SciPy sparse matrix
a square 2D array that gives the raw operation matrix, assumed to
be in the `mx_basis` basis, to parameterize. The shape of this
array sets the dimension of the operation. If None, then it is assumed
equal to `unitary_postfactor` (which cannot also be None). The
quantity `op_matrix inv(unitary_postfactor)` is parameterized via
projection onto the Lindblad terms.
ham_basis : {'std', 'gm', 'pp', 'qt'}, list of matrices, or Basis object
The basis is used to construct the Hamiltonian-type lindblad error
Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp),
and Qutrit (qt), list of numpy arrays, or a custom basis object.
nonham_basis : {'std', 'gm', 'pp', 'qt'}, list of matrices, or Basis object
The basis is used to construct the non-Hamiltonian (generalized
Stochastic-type) lindblad error Allowed values are Matrix-unit
(std), Gell-Mann (gm), Pauli-product (pp), and Qutrit (qt), list of
numpy arrays, or a custom basis object.
param_mode : {"unconstrained", "cptp", "depol", "reldepol"}
Describes how the Lindblad coefficients/projections relate to the
operation's parameter values. Allowed values are:
`"unconstrained"` (coeffs are independent unconstrained parameters),
`"cptp"` (independent parameters but constrained so map is CPTP),
`"reldepol"` (all non-Ham. diagonal coeffs take the *same* value),
`"depol"` (same as `"reldepol"` but coeffs must be *positive*)
nonham_mode : {"diagonal", "diag_affine", "all"}
Which non-Hamiltonian Lindblad projections are potentially non-zero.
Allowed values are: `"diagonal"` (only the diagonal Lind. coeffs.),
`"diag_affine"` (diagonal coefficients + affine projections), and
`"all"` (the entire matrix of coefficients is allowed).
truncate : bool, optional
Whether to truncate the projections onto the Lindblad terms in
order to meet constraints (e.g. to preserve CPTP) when necessary.
If False, then an error is thrown when the given `operation` cannot
be realized by the specified set of Lindblad projections.
mx_basis : {'std', 'gm', 'pp', 'qt'} or Basis object
The source and destination basis, respectively. Allowed
values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp),
and Qutrit (qt) (or a custom basis object).
evotype : Evotype or str, optional
The evolution type. The special value `"default"` is equivalent
to specifying the value of `pygsti.evotypes.Evotype.default_evotype`.
state_space : TODO docstring
Returns
-------
LindbladOp
"""
#Compute an errorgen from the given op_matrix. Works with both
# dense and sparse matrices.
sparseOp = _sps.issparse(op_matrix)
#Init base from error generator: sets basis members and ultimately
# the parameters in self.paramvals
if sparseOp:
#Instead of making error_generator(...) compatible with sparse matrices
# we require sparse matrices to have trivial initial error generators
# or we convert to dense:
if _mt.safe_norm(op_matrix - _sps.identity(op_matrix.shape[0], 'd')) < 1e-8:
errgenMx = _sps.csr_matrix(op_matrix.shape, dtype='d') # all zeros
else:
errgenMx = _sps.csr_matrix(
_ot.error_generator(op_matrix.toarray(), _np.identity(op_matrix.shape[0], 'd'),
mx_basis, "logGTi"), dtype='d')
else:
errgenMx = _ot.error_generator(op_matrix, _np.identity(op_matrix.shape[0], 'd'),
mx_basis, "logGTi")
return cls.from_error_generator(errgenMx, parameterization, lindblad_basis,
mx_basis, truncate, evotype, state_space=state_space)
@classmethod
def from_error_generator(cls, errgen_or_dim, parameterization="CPTP", lindblad_basis='PP', mx_basis='pp',
truncate=True, evotype="default", state_space=None):
"""
TODO: docstring - take from now-private version below Note: errogen_or_dim can be an integer => zero errgen
"""
errgen = _np.zeros((errgen_or_dim, errgen_or_dim), 'd') \
if isinstance(errgen_or_dim, (int, _np.int64)) else errgen_or_dim
return cls._from_error_generator(errgen, parameterization, lindblad_basis,
mx_basis, truncate, evotype, state_space)
@classmethod
def from_error_generator_and_blocks(cls, errgen_or_dim, lindblad_coefficient_blocks,
lindblad_basis='PP', mx_basis='pp',
truncate=True, evotype="default", state_space=None):
"""
TODO: docstring - take from now-private version below Note: errogen_or_dim can be an integer => zero errgen
"""
errgenMx = _np.zeros((errgen_or_dim, errgen_or_dim), 'd') \
if isinstance(errgen_or_dim, (int, _np.int64)) else errgen_or_dim
for blk in lindblad_coefficient_blocks:
blk.set_from_errorgen_projections(errgenMx, mx_basis, truncate=truncate)
return cls(lindblad_coefficient_blocks, lindblad_basis, mx_basis, evotype, state_space)
@classmethod
def _from_error_generator(cls, errgen, parameterization="CPTP", lindblad_basis="PP",
mx_basis="pp", truncate=True, evotype="default", state_space=None):
"""
Create a Lindblad-form error generator from an error generator matrix and a basis.
TODO: fix docstring -- ham/nonham_basis ==> lindblad_basis
The basis specifies how to decompose (project) the error generator.
Parameters
----------
errgen : numpy array or SciPy sparse matrix
a square 2D array that gives the full error generator. The shape of
this array sets the dimension of the operator. The projections of
this quantity onto the `ham_basis` and `nonham_basis` are closely
related to the parameters of the error generator (they may not be
exactly equal if, e.g `cptp=True`).
ham_basis: {'std', 'gm', 'pp', 'qt'}, list of matrices, or Basis object
The basis is used to construct the Hamiltonian-type lindblad error
Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp),
and Qutrit (qt), list of numpy arrays, or a custom basis object.
nonham_basis: {'std', 'gm', 'pp', 'qt'}, list of matrices, or Basis object
The basis is used to construct the non-Hamiltonian-type lindblad error
Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp),
and Qutrit (qt), list of numpy arrays, or a custom basis object.
param_mode : {"unconstrained", "cptp", "depol", "reldepol"}
Describes how the Lindblad coefficients/projections relate to the
operation's parameter values. Allowed values are:
`"unconstrained"` (coeffs are independent unconstrained parameters),
`"cptp"` (independent parameters but constrained so map is CPTP),
`"reldepol"` (all non-Ham. diagonal coeffs take the *same* value),
`"depol"` (same as `"reldepol"` but coeffs must be *positive*)
nonham_mode : {"diagonal", "diag_affine", "all"}
Which non-Hamiltonian Lindblad projections are potentially non-zero.
Allowed values are: `"diagonal"` (only the diagonal Lind. coeffs.),
`"diag_affine"` (diagonal coefficients + affine projections), and
`"all"` (the entire matrix of coefficients is allowed).
mx_basis : {'std', 'gm', 'pp', 'qt'} or Basis object
The source and destination basis, respectively. Allowed
values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp),
and Qutrit (qt) (or a custom basis object).
truncate : bool, optional
Whether to truncate the projections onto the Lindblad terms in
order to meet constraints (e.g. to preserve CPTP) when necessary.
If False, then an error is thrown when the given `errgen` cannot
be realized by the specified set of Lindblad projections.
evotype : {"densitymx","svterm","cterm"}
The evolution type of the error generator being constructed.
`"densitymx"` means usual Lioville density-matrix-vector propagation
via matrix-vector products. `"svterm"` denotes state-vector term-
based evolution (action of operation is obtained by evaluating the rank-1
terms up to some order). `"cterm"` is similar but uses Clifford operation
action on stabilizer states.
state_space : TODO docstring
Returns
-------
LindbladErrorgen
"""
dim = errgen.shape[0]
if state_space is None:
state_space = _statespace.default_space_for_dim(dim)
#Maybe this is unnecessary now, but determine whether the bases
# given to us are sparse or not and make them all consistent
# (maybe this is needed by lindblad_errorgen_projections call below?)
sparse = None
if isinstance(lindblad_basis, _Basis):
sparse = lindblad_basis.sparse
else:
if isinstance(lindblad_basis, str): sparse = _sps.issparse(errgen)
elif len(lindblad_basis) > 0: sparse = _sps.issparse(lindblad_basis[0])
lindblad_basis = _Basis.cast(lindblad_basis, dim, sparse=sparse)
if sparse is None: sparse = False # the default
#Create or convert matrix basis to consistent sparsity
if not isinstance(mx_basis, _Basis):
matrix_basis = _Basis.cast(mx_basis, dim, sparse=sparse)
else: matrix_basis = mx_basis
# errgen + bases => coeffs
parameterization = LindbladParameterization.cast(parameterization)
# Create blocks based on bases along - no specific errorgen labels
blocks = []
for blk_type, blk_param_mode in zip(parameterization.block_types, parameterization.param_modes):
blk = _LindbladCoefficientBlock(blk_type, lindblad_basis, param_mode=blk_param_mode)
blk.set_from_errorgen_projections(errgen, matrix_basis, truncate=truncate)
blocks.append(blk)
return cls(blocks, "auto", mx_basis, evotype, state_space)
@classmethod
def from_elementary_errorgens(cls, elementary_errorgens, parameterization='auto', elementary_errorgen_basis='PP',
mx_basis="pp", truncate=True, evotype="default", state_space=None):
"""TODO: docstring"""
state_space = _statespace.StateSpace.cast(state_space)
dim = state_space.dim # Store superop dimension
basis = _Basis.cast(elementary_errorgen_basis, dim)
#convert elementary errorgen labels to *local* labels (ok to specify w/global labels)
identity_label_1Q = 'I' # maybe we could get this from a 1Q basis somewhere?
sslbls = state_space.sole_tensor_product_block_labels # first TPB labels == all labels
elementary_errorgens = _collections.OrderedDict(
[(_LocalElementaryErrorgenLabel.cast(lbl, sslbls, identity_label_1Q), val)
for lbl, val in elementary_errorgens.items()])
parameterization = LindbladParameterization.minimal_from_elementary_errorgens(elementary_errorgens) \
if parameterization == "auto" else LindbladParameterization.cast(parameterization)
eegs_by_typ = {
'ham': {eeglbl: v for eeglbl, v in elementary_errorgens.items() if eeglbl.errorgen_type == 'H'},
'other_diagonal': {eeglbl: v for eeglbl, v in elementary_errorgens.items() if eeglbl.errorgen_type == 'S'},
'other': {eeglbl: v for eeglbl, v in elementary_errorgens.items() if eeglbl.errorgen_type != 'H'}
}
blocks = []
for blk_type, blk_param_mode in zip(parameterization.block_types, parameterization.param_modes):
relevant_eegs = eegs_by_typ[blk_type] # KeyError => unrecognized block type!
bels = sorted(set(_itertools.chain(*[lbl.basis_element_labels for lbl in relevant_eegs.keys()])))
blk = _LindbladCoefficientBlock(blk_type, basis, bels, param_mode=blk_param_mode)
blk.set_elementary_errorgens(relevant_eegs, truncate=truncate)
blocks.append(blk)
return cls(blocks, basis, mx_basis, evotype, state_space)
def __init__(self, lindblad_coefficient_blocks, lindblad_basis='auto', mx_basis='pp',
evotype="default", state_space=None):
if isinstance(lindblad_coefficient_blocks, dict): # backward compat warning
_warnings.warn(("You're trying to create a LindbladErrorgen object using a dictionary. This"
" constructor was recently updated to take a list of LindbladCoefficientBlock"
" objects (not a dict) for increased flexibility. You probably want to call"
" a LindbladErrorgen.from_elementary_errorgens(...) instead."))
state_space = _statespace.StateSpace.cast(state_space)
#Decide on our rep-type ahead of time so we know whether to make bases sparse
# (a LindbladErrorgen with a sparse rep => sparse bases and similar with dense rep)
evotype = _Evotype.cast(evotype)
reptype_preferences = ('lindblad errorgen', 'dense superop', 'sparse superop') \
if evotype.prefer_dense_reps else ('lindblad errorgen', 'sparse superop', 'dense superop')
for reptype in reptype_preferences:
if evotype.supports(reptype):
self._rep_type = reptype; break
else:
raise ValueError("Evotype doesn't support any of the representations a LindbladErrorgen requires.")
sparse_bases = bool(self._rep_type == 'sparse superop') # we use sparse bases iff we have a sparse rep
state_space = _statespace.StateSpace.cast(state_space)
dim = state_space.dim # Store superop dimension
#UPDATE: no more self.lindblad_basis
#self.lindblad_basis = _Basis.cast(lindblad_basis, dim, sparse=sparse_bases)
if lindblad_basis == "auto":
assert(all([(blk._basis is not None) for blk in lindblad_coefficient_blocks])), \
"When `lindblad_basis == 'auto'`, the supplied coefficient blocks must have valid bases!"
default_lindblad_basis = None
else:
default_lindblad_basis = _Basis.cast(lindblad_basis, dim, sparse=sparse_bases)
for blk in lindblad_coefficient_blocks:
if blk._basis is None: blk._basis = default_lindblad_basis
elif blk._basis.sparse != sparse_bases: # update block bases to desired sparsity if needed
blk._basis = blk._basis.with_sparsity(sparse_bases)
#UPDATE - this essentially constructs the coefficient blocks from a single dict, which are now given as input
## lindblad_term_dict, basis => bases + parameter values
## but maybe we want lindblad_term_dict, basisdict => basis + projections/coeffs,
## then projections/coeffs => paramvals? since the latter is what set_errgen needs
#hamC, otherC, self.ham_basis, self.other_basis = \
# _ot.lindblad_terms_to_projections(lindblad_term_dict, self.lindblad_basis,
# self.parameterization.nonham_mode)
#UPDATE - self.ham_basis_size and self.other_basis_size have been removed!
#self.ham_basis_size = len(self.ham_basis)
#self.other_basis_size = len(self.other_basis)
#assert(self.parameterization.ham_params_allowed or self.ham_basis_size == 0), \
# "Hamiltonian lindblad terms are not allowed!"
#assert(self.parameterization.nonham_params_allowed or self.other_basis_size == 0), \
# "Non-Hamiltonian lindblad terms are not allowed!"
#
## Check that bases have the desired sparseness (should be same as lindblad_basis)
#assert (self.ham_basis_size == 0 or self.ham_basis.sparse == sparse_bases)
#assert (self.other_basis_size == 0 or self.other_basis.sparse == sparse_bases)
self.coefficient_blocks = lindblad_coefficient_blocks
self.matrix_basis = _Basis.cast(mx_basis, dim, sparse=sparse_bases)
nP = sum([blk.num_params for blk in lindblad_coefficient_blocks])
self.paramvals = _np.empty(nP, 'd'); off = 0
for blk in lindblad_coefficient_blocks:
self.paramvals[off:off + blk.num_params] = blk.to_vector()
off += blk.num_params
#Fast CSR-matrix summing variables: N/A if not sparse or using terms
self._CSRSumIndices = self._CSRSumData = self._CSRSumPtr = None
# Generator matrices & cache qtys: N/A for term-based evotypes
#TODO - maybe move some/all of these to the coefficient block class:
self._onenorm_upbound = None
self._coefficient_weights = None
#All representations need to track 1norms:
self.lindblad_term_superops_and_1norms = [
blk.create_lindblad_term_superoperators(self.matrix_basis, sparse_bases, include_1norms=True, flat=True)
for blk in lindblad_coefficient_blocks]
#Create a representation of the type chosen above:
if self._rep_type == 'lindblad errorgen':
rep = evotype.create_lindblad_errorgen_rep(lindblad_coefficient_blocks, state_space)
else: # Otherwise create a sparse or dense matrix representation
if sparse_bases: # then construct a sparse-matrix representation (self._rep_type == 'sparse superop')
#Precompute for faster CSR sums in _construct_errgen
all_csr_matrices = list(_itertools.chain.from_iterable(
[superops for superops, norms in self.lindblad_term_superops_and_1norms]))
flat_dest_indices, flat_src_data, flat_nnzptr, indptr, indices, N = \
_mt.csr_sum_flat_indices(all_csr_matrices)
self._CSRSumIndices = flat_dest_indices
self._CSRSumData = flat_src_data
self._CSRSumPtr = flat_nnzptr
self._data_scratch = _np.zeros(len(indices), complex) # *complex* scratch space for updating rep
rep = evotype.create_sparse_rep(_np.ascontiguousarray(_np.zeros(len(indices), 'd')),
_np.ascontiguousarray(indices, _np.int64),
_np.ascontiguousarray(indptr, _np.int64),
state_space)
else: # self._rep_type = 'dense superop'
# UNSPECIFIED BASIS -- we set basis=None below, which may not work with all evotypes,
# and should be replaced with the basis of contained ops (if any) once we establish
# a common .basis or ._basis attribute of representations (which could still be None)
# Update: fixed now (I think) - this seems like a legit matrix_basis to use... REMOVE comment?
rep = evotype.create_dense_superop_rep(None, self.matrix_basis, state_space)
_LinearOperator.__init__(self, rep, evotype) # sets self.dim
self._update_rep() # updates _rep whether it's a dense or sparse matrix
self._paramlbls = _np.array(list(_itertools.chain.from_iterable(
[blk.param_labels for blk in self.coefficient_blocks])), dtype=object)
assert(self._onenorm_upbound is not None) # _update_rep should set this
#Done with __init__(...)
#def _init_generators(self, dim):
# #assumes self.dim, self.ham_basis, self.other_basis, and self.matrix_basis are setup...
# sparse_bases = bool(self._rep_type == 'sparse superop')
#
# #HERE TODO - need to update this / MOVE to block class?
# #use caching to increase performance - cache based on all the self.XXX members utilized by this fn
# cache_key = (self._rep_type, self.matrix_basis, self.ham_basis, self.other_basis, self.parameterization)
# #print("cache key = ",self._rep_type, (self.matrix_basis.name, self.matrix_basis.dim),
# # (self.ham_basis.name, self.ham_basis.dim), (self.other_basis.name, self.other_basis.dim),
# # str(self.parameterization))
#
# if cache_key not in self._generators_cache:
#
# d = int(round(_np.sqrt(dim)))
# assert(d * d == dim), "Errorgen dim must be a perfect square"
#
# # Get basis transfer matrix
# mxBasisToStd = self.matrix_basis.create_transform_matrix(
# _BuiltinBasis("std", self.matrix_basis.dim, sparse_bases))
# # use BuiltinBasis("std") instead of just "std" in case matrix_basis is a TensorProdBasis
# leftTrans = _spsl.inv(mxBasisToStd.tocsc()).tocsr() if _sps.issparse(mxBasisToStd) \
# else _np.linalg.inv(mxBasisToStd)
# rightTrans = mxBasisToStd
#
# hamBasisMxs = self.ham_basis.elements
# otherBasisMxs = self.other_basis.elements
#
# hamGens, otherGens = _ot.lindblad_error_generators(
# hamBasisMxs, otherBasisMxs, normalize=False,
# other_mode=self.parameterization.nonham_mode) # in std basis
#
# # Note: lindblad_error_generators will return sparse generators when
# # given a sparse basis (or basis matrices)
#
# if hamGens is not None:
# bsH = len(hamGens) + 1 # projection-basis size (not nec. == dim)
# _ot._assert_shape(hamGens, (bsH - 1, dim, dim), sparse_bases)
#
# # apply basis change now, so we don't need to do so repeatedly later
# if sparse_bases:
# hamGens = [_mt.safe_real(_mt.safe_dot(leftTrans, _mt.safe_dot(mx, rightTrans)),
# inplace=True, check=True) for mx in hamGens]
# for mx in hamGens: mx.sort_indices()
# # for faster addition ops in _construct_errgen_matrix
# else:
# #hamGens = _np.einsum("ik,akl,lj->aij", leftTrans, hamGens, rightTrans)
# hamGens = _np.transpose(_np.tensordot(
# _np.tensordot(leftTrans, hamGens, (1, 1)), rightTrans, (2, 0)), (1, 0, 2))
# else:
# bsH = 0
# assert(bsH == self.ham_basis_size)
#
# if otherGens is not None:
#
# if self.parameterization.nonham_mode == "diagonal":
# bsO = len(otherGens) + 1 # projection-basis size (not nec. == dim)
# _ot._assert_shape(otherGens, (bsO - 1, dim, dim), sparse_bases)
#
# # apply basis change now, so we don't need to do so repeatedly later
# if sparse_bases:
# otherGens = [_mt.safe_real(_mt.safe_dot(leftTrans, _mt.safe_dot(mx, rightTrans)),
# inplace=True, check=True) for mx in otherGens]
# for mx in otherGens: mx.sort_indices()
# # for faster addition ops in _construct_errgen_matrix
# else:
# #otherGens = _np.einsum("ik,akl,lj->aij", leftTrans, otherGens, rightTrans)
# otherGens = _np.transpose(_np.tensordot(
# _np.tensordot(leftTrans, otherGens, (1, 1)), rightTrans, (2, 0)), (1, 0, 2))
#
# elif self.parameterization.nonham_mode == "diag_affine":
# # projection-basis size (not nec. == dim) [~shape[1] but works for lists too]
# bsO = len(otherGens[0]) + 1
# _ot._assert_shape(otherGens, (2, bsO - 1, dim, dim), sparse_bases)
#
# # apply basis change now, so we don't need to do so repeatedly later
# if sparse_bases:
# otherGens = [[_mt.safe_dot(leftTrans, _mt.safe_dot(mx, rightTrans))
# for mx in mxRow] for mxRow in otherGens]
#
# for mxRow in otherGens:
# for mx in mxRow: mx.sort_indices()
# # for faster addition ops in _construct_errgen_matrix
# else:
# #otherGens = _np.einsum("ik,abkl,lj->abij", leftTrans,
# # otherGens, rightTrans)
# otherGens = _np.transpose(_np.tensordot(
# _np.tensordot(leftTrans, otherGens, (1, 2)), rightTrans, (3, 0)), (1, 2, 0, 3))
#
# else:
# bsO = len(otherGens) + 1 # projection-basis size (not nec. == dim)
# _ot._assert_shape(otherGens, (bsO - 1, bsO - 1, dim, dim), sparse_bases)
#
# # apply basis change now, so we don't need to do so repeatedly later
# if sparse_bases:
# otherGens = [[_mt.safe_dot(leftTrans, _mt.safe_dot(mx, rightTrans))
# for mx in mxRow] for mxRow in otherGens]
# #Note: complex OK here, as only linear combos of otherGens (like (i,j) + (j,i)
# # terms) need to be real
#
# for mxRow in otherGens:
# for mx in mxRow: mx.sort_indices()
# # for faster addition ops in _construct_errgen_matrix
# else:
# #otherGens = _np.einsum("ik,abkl,lj->abij", leftTrans,
# # otherGens, rightTrans)
# otherGens = _np.transpose(_np.tensordot(
# _np.tensordot(leftTrans, otherGens, (1, 2)), rightTrans, (3, 0)), (1, 2, 0, 3))
#
# else:
# bsO = 0
# assert(bsO == self.other_basis_size)
#
# if hamGens is not None:
# hamGens_1norms = _np.array([_mt.safe_onenorm(mx) for mx in hamGens], 'd')
# else:
# hamGens_1norms = None
#
# if otherGens is not None:
# if self.parameterization.nonham_mode == "diagonal":
# otherGens_1norms = _np.array([_mt.safe_onenorm(mx) for mx in otherGens], 'd')
# else:
# otherGens_1norms = _np.array([_mt.safe_onenorm(mx)
# for oGenRow in otherGens for mx in oGenRow], 'd')
# else:
# otherGens_1norms = None
#
# self._generators_cache[cache_key] = (hamGens, otherGens, hamGens_1norms, otherGens_1norms)
#
# cached_hamGens, cached_otherGens, cached_h1norms, cached_o1norms = self._generators_cache[cache_key]
# return (_copy.deepcopy(cached_hamGens), _copy.deepcopy(cached_otherGens),
# cached_h1norms.copy() if (cached_h1norms is not None) else None,
# cached_o1norms.copy() if (cached_o1norms is not None) else None)
def _init_terms(self, coefficient_blocks, max_polynomial_vars):
Lterms = []; off = 0
for blk in self.coefficient_blocks:
Lterms.extend(blk.create_lindblad_term_objects(off, max_polynomial_vars, self._evotype, self.state_space))
off += blk.num_params
#Make compact polys that are ready to (repeatedly) evaluate (useful
# for term-based calcs which call total_term_magnitude() a lot)
poly_coeffs = [t.coeff for t in Lterms]
tapes = [poly.compact(complex_coeff_tape=True) for poly in poly_coeffs]
if len(tapes) > 0:
vtape = _np.concatenate([t[0] for t in tapes])
ctape = _np.concatenate([t[1] for t in tapes])
else:
vtape = _np.empty(0, _np.int64)
ctape = _np.empty(0, complex)
coeffs_as_compact_polys = (vtape, ctape)
#DEBUG TODO REMOVE (and make into test) - check norm of rank-1 terms
# (Note: doesn't work for Clifford terms, which have no .base):
# rho =OP=> coeff * A rho B
# want to bound | coeff * Tr(E Op rho) | = | coeff | * | <e|A|psi><psi|B|e> |
# so A and B should be unitary so that | <e|A|psi><psi|B|e> | <= 1
# but typically these are unitaries / (sqrt(2)*nqubits)
#import bpdb; bpdb.set_trace()
#scale = 1.0
#for t in Lterms:
# for op in t._rep.pre_ops:
# test = _np.dot(_np.conjugate(scale * op.base.T), scale * op.base)
# assert(_np.allclose(test, _np.identity(test.shape[0], 'd')))
# for op in t._rep.post_ops:
# test = _np.dot(_np.conjugate(scale * op.base.T), scale * op.base)
# assert(_np.allclose(test, _np.identity(test.shape[0], 'd')))
return Lterms, coeffs_as_compact_polys
def _set_params_from_matrix(self, errgen, truncate):
""" Sets self.paramvals based on `errgen` """
# Project errgen to give coefficient block data
remaining_errgen = errgen
for blk in self.coefficient_blocks:
projected_errgen = blk.set_from_errorgen_projections(remaining_errgen, self.matrix_basis,
return_projected_errorgen=True, truncate=truncate)
remaining_errgen = remaining_errgen - projected_errgen
# set paramvals from coefficient block data
off = 0
for blk in self.coefficient_blocks:
self.paramvals[off:off + blk.num_params] = blk.to_vector()
off += blk.num_params
self._update_rep()
#assert(_np.allclose(errgen, self.to_dense())) # DEBUG
def _update_rep(self):
"""
Updates self._rep, which contains a representation of this error generator
as either a dense or sparse matrix. This routine essentially builds the
error generator matrix using the current coefficient block data (which from_vector
should keep in sync with the parameters) and updates self._rep accordingly (by
rewriting its data).
"""
# Update 1-norm of composite errorgen
onenorm = sum([_np.dot(_np.abs(blk.block_data.flat), one_norms) for blk, (_, one_norms)
in zip(self.coefficient_blocks, self.lindblad_term_superops_and_1norms)])
assert(_np.imag(onenorm) < 1e-6)
onenorm = _np.real(onenorm)
# Build operation matrix from generators and coefficients:
if self._rep_type == 'lindblad errorgen':
# the code below is for updating sparse or dense matrix representations. If our
# evotype has a native Lindblad representation, maybe in the FUTURE we should
# call an update method of it here?
#Still need to update onenorm - FUTURE: maybe put this logic inside rep? (done above now)
#onenorm = sum([_np.dot(blk.block_data.flat, one_norms) for blk, (_, one_norms)
# in zip(self.coefficient_blocks, self.lindblad_term_superops_and_1norms)])
pass
elif self._rep_type == 'sparse superop': # then bases & errgen are sparse
coeffs = None
data = self._data_scratch
data.fill(0.0) # data starts at zero
# Get coefficients in a single flat array (Note: this can be complex)
coeffs = _np.array(list(_itertools.chain(*[blk.block_data.flat for blk in self.coefficient_blocks])))
if len(coeffs) > 0:
_mt.csr_sum_flat(data, coeffs, self._CSRSumIndices, self._CSRSumData, self._CSRSumPtr)
#Don't perform this check as this function is called a *lot* and it
# could adversely impact performance
#assert(_np.isclose(_np.linalg.norm(data.imag), 0)), \
# "Imaginary error gen norm: %g" % _np.linalg.norm(data.imag)
#Update the rep's sparse matrix data stored in self._rep_data (the rep already
# has the correct sparse matrix structure, as given by indices and indptr in
# __init__, so we just update the *data* array).
self._rep.data[:] = data.real
else: # dense matrices
lnd_error_gen = sum([_np.tensordot(blk.block_data.flat, Lterm_superops, (0, 0)) for blk, (Lterm_superops, _)
in zip(self.coefficient_blocks, self.lindblad_term_superops_and_1norms)])
assert(_np.isclose(_np.linalg.norm(lnd_error_gen.imag), 0)), \
"Imaginary error gen norm: %g" % _np.linalg.norm(lnd_error_gen.imag)
#print("errgen pre-real = \n"); _mt.print_mx(lnd_error_gen,width=4,prec=1)
self._rep.base[:, :] = lnd_error_gen.real
self._onenorm_upbound = onenorm
#assert(self._onenorm_upbound >= _np.linalg.norm(self.to_dense(), ord=1) - 1e-6) #DEBUG
def to_dense(self, on_space='minimal'):
"""
Return this error generator as a dense matrix.
Parameters
----------
on_space : {'minimal', 'Hilbert', 'HilbertSchmidt'}
The space that the returned dense operation acts upon. For unitary matrices and bra/ket vectors,
use `'Hilbert'`. For superoperator matrices and super-bra/super-ket vectors use `'HilbertSchmidt'`.
`'minimal'` means that `'Hilbert'` is used if possible given this operator's evolution type, and
otherwise `'HilbertSchmidt'` is used.
Returns
-------
numpy.ndarray
"""
if self._rep_type == 'lindblad errorgen':
assert(on_space in ('minimal', 'HilbertSchmidt'))
lnd_error_gen = sum([_np.tensordot(blk.block_data.flat, Lterm_superops, (0, 0)) for blk, (Lterm_superops, _)
in zip(self.coefficient_blocks, self.lindblad_term_superops_and_1norms)])
assert(_np.isclose(_np.linalg.norm(lnd_error_gen.imag), 0)), \
"Imaginary error gen norm: %g" % _np.linalg.norm(lnd_error_gen.imag)
return lnd_error_gen.real
elif self._rep_type == 'sparse superop':
return self.to_sparse(on_space).toarray()
else: # dense rep
return self._rep.to_dense(on_space)
def to_sparse(self, on_space='minimal'):
"""
Return the error generator as a sparse matrix.
Returns
-------
scipy.sparse.csr_matrix
"""
if self._rep_type == 'lindblad errorgen':
return _sps.csr_matrix(self.to_dense(on_space))
elif self._rep_type == 'sparse superop':
assert(on_space in ('minimal', 'HilbertSchmidt'))
return _sps.csr_matrix((self._rep.data, self._rep.indices, self._rep.indptr),
shape=(self.dim, self.dim))
else: # dense rep
return _sps.csr_matrix(self.to_dense(on_space))
#def torep(self):
# """
# Return a "representation" object for this error generator.
#
# Such objects are primarily used internally by pyGSTi to compute
# things like probabilities more efficiently.
#
# Returns
# -------
# OpRep
# """
# if self._evotype == "densitymx":
# if self._rep_type == 'sparse superop':
# A = self.err_gen_mx
# return replib.DMOpRepSparse(
# _np.ascontiguousarray(A.data),
# _np.ascontiguousarray(A.indices, _np.int64),
# _np.ascontiguousarray(A.indptr, _np.int64))
# else:
# return replib.DMOpRepDense(_np.ascontiguousarray(self.err_gen_mx, 'd'))
# else:
# raise NotImplementedError("torep(%s) not implemented for %s objects!" %
# (self._evotype, self.__class__.__name__))
def taylor_order_terms(self, order, max_polynomial_vars=100, return_coeff_polys=False):
"""
Get the `order`-th order Taylor-expansion terms of this operation.
This function either constructs or returns a cached list of the terms at
the given order. Each term is "rank-1", meaning that its action on a
density matrix `rho` can be written:
`rho -> A rho B`
The coefficients of these terms are typically polynomials of the operation's
parameters, where the polynomial's variable indices index the *global*
parameters of the operation's parent (usually a :class:`Model`), not the
operation's local parameter array (i.e. that returned from `to_vector`).
Parameters
----------
order : int
The order of terms to get.
max_polynomial_vars : int, optional
maximum number of variables the created polynomials can have.
return_coeff_polys : bool
Whether a parallel list of locally-indexed (using variable indices
corresponding to *this* object's parameters rather than its parent's)
polynomial coefficients should be returned as well.
Returns
-------
terms : list
A list of :class:`RankOneTerm` objects.
coefficients : list
Only present when `return_coeff_polys == True`.
A list of *compact* polynomial objects, meaning that each element
is a `(vtape,ctape)` 2-tuple formed by concatenating together the
output of :method:`Polynomial.compact`.
"""
assert(self._rep_type == 'lindblad errorgen'), \
"Only evotypes with native Lindblad errorgen representations can utilize Taylor terms"
assert(order == 0), \
"Error generators currently treat all terms as 0-th order; nothing else should be requested!"
assert(return_coeff_polys is False)
if self._rep.Lterms is None:
Lblocks = self._rep.lindblad_coefficient_blocks
self._rep.Lterms, self._rep.Lterm_coeffs = self._init_terms(Lblocks, max_polynomial_vars)
return self._rep.Lterms # terms with local-index polynomial coefficients
#def get_direct_order_terms(self, order): # , order_base=None - unused currently b/c order is always 0...
# v = self.to_vector()
# poly_terms = self.get_taylor_order_terms(order)
# return [ term.evaluate_coeff(v) for term in poly_terms ]
@property
def total_term_magnitude(self):
"""
Get the total (sum) of the magnitudes of all this operator's terms.
The magnitude of a term is the absolute value of its coefficient, so
this function returns the number you'd get from summing up the
absolute-coefficients of all the Taylor terms (at all orders!) you
get from expanding this operator in a Taylor series.
Returns
-------
float
"""
# return (sum of absvals of term coeffs)
assert(self._rep.Lterms is not None), "Must call `taylor_order_terms` before calling total_term_magnitude!"
vtape, ctape = self._rep.Lterm_coeffs
return _abs_sum_bulk_eval_compact_polynomials_complex(vtape, ctape, self.to_vector(), len(self._rep.Lterms))
@property
def total_term_magnitude_deriv(self):
"""
The derivative of the sum of *all* this operator's terms.
Computes the derivative of the total (sum) of the magnitudes of all this
operator's terms with respect to the operators (local) parameters.
Returns
-------
numpy array
An array of length self.num_params
"""
# In general: d(|x|)/dp = d( sqrt(x.r^2 + x.im^2) )/dp = (x.r*dx.r/dp + x.im*dx.im/dp) / |x| = Re(x * conj(dx/dp))/|x| # noqa: E501
# The total term magnitude in this case is sum_i( |coeff_i| ) so we need to compute:
# d( sum_i( |coeff_i| )/dp = sum_i( d(|coeff_i|)/dp ) = sum_i( Re(coeff_i * conj(d(coeff_i)/dp)) / |coeff_i| )
wrtInds = _np.ascontiguousarray(_np.arange(self.num_params), _np.int64) # for Cython arg mapping
vtape, ctape = self._rep.Lterm_coeffs
coeff_values = _bulk_eval_compact_polynomials_complex(vtape, ctape, self.to_vector(), (len(self._rep.Lterms),))
coeff_deriv_polys = _compact_deriv(vtape, ctape, wrtInds)
coeff_deriv_vals = _bulk_eval_compact_polynomials_complex(coeff_deriv_polys[0], coeff_deriv_polys[1],
self.to_vector(), (len(self._rep.Lterms),
len(wrtInds)))
abs_coeff_values = _np.abs(coeff_values)
abs_coeff_values[abs_coeff_values < 1e-10] = 1.0 # so ratio is 0 in cases where coeff_value == 0
ret = _np.sum(_np.real(coeff_values[:, None] * _np.conj(coeff_deriv_vals))
/ abs_coeff_values[:, None], axis=0) # row-sum
assert(_np.linalg.norm(_np.imag(ret)) < 1e-8)
return ret.real
#DEBUG
#ret2 = _np.empty(self.num_params,'d')
#eps = 1e-8
#orig_vec = self.to_vector().copy()
#f0 = sum([abs(coeff) for coeff in coeff_values])
#for i in range(self.num_params):
# v = orig_vec.copy()
# v[i] += eps
# new_coeff_values = _bulk_eval_compact_polynomials_complex(vtape, ctape, v, (len(self.Lterms),))
# ret2[i] = ( sum([abs(coeff) for coeff in new_coeff_values]) - f0 ) / eps
#test3 = _np.linalg.norm(ret-ret2)
#print("TEST3 = ",test3)
#if test3 > 10.0:
# import bpdb; bpdb.set_trace()
#return ret
@property
def num_params(self):
"""
Get the number of independent parameters which specify this operation.
Returns
-------
int
the number of independent parameters.
"""
return len(self.paramvals)
def to_vector(self):
"""
Extract a vector of the underlying operation parameters from this operation.
Returns
-------
numpy array
a 1D numpy array with length == num_params().
"""
return self.paramvals
def from_vector(self, v, close=False, dirty_value=True):
"""
Initialize the operation using a vector of parameters.
Parameters
----------
v : numpy array
The 1D vector of operation parameters. Length
must == num_params()
close : bool, optional
Whether `v` is close to this operation's current
set of parameters. Under some circumstances, when this
is true this call can be completed more quickly.
dirty_value : bool, optional
The value to set this object's "dirty flag" to before exiting this
call. This is passed as an argument so it can be updated *recursively*.
Leave this set to `True` unless you know what you're doing.
Returns
-------
None
"""
assert(len(v) == self.num_params)
self.paramvals[:] = v
off = 0
for blk in self.coefficient_blocks:
blk.from_vector(self.paramvals[off: off + blk.num_params])
off += blk.num_params
self._update_rep()
self.dirty = dirty_value
def coefficients(self, return_basis=False, logscale_nonham=False):
"""
TODO: docstring
Constructs a dictionary of the Lindblad-error-generator coefficients of this error generator.