-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPO.py
More file actions
2389 lines (1934 loc) · 89.9 KB
/
PO.py
File metadata and controls
2389 lines (1934 loc) · 89.9 KB
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
# ------------------------------------------------------------------------
# Class : PO
# Description : Functions for Product Operator Formalism of spin-1/2
# Tested : Python 3.8.5, SymPy 1.11.2, NumPy 1.23.3
# Developer : Dr. Kosuke Ohgo
# ULR : https://github.com/ohgo1977/PO_Python
# Version : 1.4.0
#
# Please read the manual (PO_Python_Manual.pdf) for details.
#
# ------------------------------------------------------------------------
#
# MIT License
#
# Copyright (c) 2025 Kosuke Ohgo
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Version 1.4.0
# Revised on 11/14/2025
# Modified pfg() for the flexibility of the gradeint names.
# Modified sections of the manual related to pfg() and dephase().
#
# Version 1.3.0
# Revised on 8/18/2023
# Added a switch (PO.simp) for the simplification method between simplify(), TR8(), and fu().
# This is a class variable.
#
# Version 1.2.0
# Revised on 8/16/2023
# TR8 is implemented as a replacement of simplify() in CombPO, dispPO(), dephase(), and SigAmp() .
#
# Version 1.1.0
# Revised on 5/25/2023
# Added simplify_exp(self), simplify_cos(self), findcoef(self, coef_in_cell)
#
# Version 1.0.1
# Revised on 5/24/2023
# findterm(), Terms including a number (i.e., 'I1x') were not recognized.
print("Hello from PO.py ver1.4.0!\n")
from sympy import exp, cos, sin, pi, symbols, I
from sympy import init_printing, Wild
from sympy.simplify.fu import TR8
import sympy as sym
import copy
from math import log2
import numpy as np
import sys
# from ttictoc import tic,toc # For time measurements using tic() and print(toc()).
# init_printing(use_unicode = False, wrap_line = False)
init_printing()
class PO:
spin_label_cell_default = ['I', 'S', 'K', 'L', 'M']
# Default labels for spin_label
simp = 'simplify'
__index_switch = 1
if __index_switch != 1 and __index_switch != 0:
__index_switch = 1
if __index_switch == 0:
print('! CAUTION !')
print('__index_switch is set as ', __index_switch)
print('The input index for spin types becomes 0-based')
print('Example:')
print('I-S spin system: 0 for I and 1 for S with 0-based index')
print('sp2id(), jc1(), symcoef(), findterm(), observable(), dispPO() will include actual codes to convert from 1-based to 0-based.')
# In pulse() and cs(), sp2id() converts 1-based index to 0-based index if PO.__index_switch is 1.
# In selPO() and delPO(), findterm() converts 1-based index to 0-based index if PO.__index_switch is 1.
asterisk_bin = 0
if asterisk_bin != 1 and asterisk_bin != 0:
asterisk_bin = 0
print(asterisk_bin)
# Control to the asterisk '*' between spin operators in the txt property.
# 0 :w/o, 1: w/ '*'.
def __init__(self, spin_no, spin_label, axis, coef, basis):
self.axis = axis # np.matrix
self.coef = sym.Matrix([coef]) # sym.Matrix
self.spin_no = spin_no
self.spin_label = spin_label # List
self.basis = basis
self.disp = 1
self.PFGq = sym.Matrix([0]) # Initial value, is there a better initial value?
self.Ncoef = PO.getNcoef(self) # np.array
self.txt = PO.getTxt(self)
self.logs = self.txt
self.M = PO.getM(self) # sym.Matrix
self.coherence = PO.getCoherence(self) # sym.Matrix
def __str__(self):
return self.txt
def getTxt(self):
# Generating a text describing the current spin operators.
# If PO.asterisk_bin is 1, '*' will be added among spin operators and coeffcients, i.e.,
# 2*Ix*Sx*cos(q).
spin_label = self.spin_label
axis = self.axis
Ncoef = self.Ncoef
coef = self.coef
if coef == sym.Matrix([0]):
txt = '0'
else:
for ii in range(axis.shape[0]):
axis_vec = np.array(axis)[ii]
pt = PO.axis2pt(spin_label, axis_vec)
if Ncoef[ii] == 0.5:
Ncoef_t = '1/2'
if PO.asterisk_bin == 1:
Ncoef_t = Ncoef_t + '*'
elif Ncoef[ii] == 1.0:
Ncoef_t = ''
else:
Ncoef_t = str(Ncoef[ii].astype(np.int64))
if PO.asterisk_bin == 1:
Ncoef_t = Ncoef_t + '*'
# coef_t = '*' + '(' + str(sym.simplify(coef[ii])) + ')' # Makes program slow
coef_t = '*' + '(' + str(coef[ii]) + ')'
if coef_t == '*(1)':
coef_t = ''
if ii == 0:
if coef_t != '*(0)':
txt = Ncoef_t + pt + coef_t
else:
txt = ''
else:
if coef_t != '*(0)':
txt = txt + ' + ' + Ncoef_t + pt + coef_t
return txt
def axis2pt(spin_label, axis_vec):
# Generating a text of a spin operator corresponding to axis_vec.
# axis_vec should be a np.array.
# Example.
# spin_label = ['I', 'S'] and axis_vec = [3, 3] => pt = 'IzSz'
#
# If PO.asterisk_bin is 1, '*' will be added between spin operators, i.e.,
# pt = 'Iz*Sz'
pt = ''
if np.count_nonzero(axis_vec, axis=0) == 0:
pt = 'E'
else:
jj_int = 0
for jj in range(len(np.array(axis_vec))):
axis_v = axis_vec[jj]
st = spin_label[jj]
if axis_v != 0:
jj_int += 1
if axis_v == 1:
at = 'x'
elif axis_v == 2:
at = 'y'
elif axis_v == 3:
at = 'z'
elif axis_v == 4:
at = 'p'
elif axis_v == 5:
at = 'm'
elif axis_v == 6:
at = 'a'
elif axis_v == 7:
at = 'b'
# end
if PO.asterisk_bin == 0:
pt = pt + st + at
elif PO.asterisk_bin == 1: # Iz*Sz style
if jj_int == 1:
pt = pt + st + at
else:
pt = pt + '*' + st + at
# end
# end
return pt
def getNcoef(self):
# Calculating coeffcients corresponding to 2^(N-1) where N is the number of the active spins.
axis = self.axis
basis = self.basis
if basis == 'xyz':
Ncoef = np.float_power(2, (np.count_nonzero(np.array(axis), axis=1)-1))
# 2^(N-1)
# Examples
# IxSx => N = 2 => Ncoef = 2
# IxSxKx => N = 3 => Ncoef = 4
elif basis == 'pmz' or basis == 'pol':
Ncoef = np.array([1]*axis.shape[0])
# Ncoef = np.transpose(np.matrix(Ncoef)) # This line will affet getM().
return Ncoef
def getM(self):
# Calculating a matrix form of the current density operator.
# The output matrix M_out is a sym class.
axis = self.axis
Ncoef = self.Ncoef
coef = self.coef
for ii in range(axis.shape[0]):
axis_tmp = axis[ii]
for jj in range(axis_tmp.shape[1]):
v_tmp = axis_tmp[0,jj]
Ma = PO.axis2M(v_tmp)
if jj == 0:
M_tmp = Ma
else:
M_tmp = np.kron(M_tmp, Ma)
M_tmp = sym.Matrix(M_tmp) # Convert to Symbolic.
Mo = M_tmp * 2 ** (axis_tmp.shape[1] - np.count_nonzero(axis_tmp)) * Ncoef[ii] * coef[ii]
if ii == 0:
M_out = Mo
else:
M_out = M_out + Mo
# M_out = sym.simplify(M_out) # It makes the program slow.
# M_out = sym.nsimplify(M_out, rational=True) # It makes the program slow.
return M_out
def getCoherence(self):
# Calculating a matrix swhoing coherences included in the current density operator.
# Note that 'u' (up) and 'd' (down) are used instead of 'p' (+) and 'm' (-) in this display.
# Details are in PO.rho_box().
spin_no = self.spin_no
coherence_out = PO.rho_box(spin_no)[0]
for ii in range(2**spin_no):
for jj in range(2**spin_no):
if self.M[ii,jj] == 0:
coherence_out[ii,jj] = 0
return coherence_out
def CombPO(self):
# Combining coeffcieints of same type of terms in a PO-class object.
axis_in = self.axis
coef_in = self.coef
# https://stackoverflow.com/questions/36068437/is-there-python-equivalent-to-matlab-unique
c, IA, IC = np.unique(axis_in,return_index=True,return_inverse=True, axis=0)
coef_out = sym.Matrix(np.zeros((len(IA), 1)))
axis_out = np.matrix(np.zeros((len(IA), axis_in.shape[1]))).astype(int)
for ii in range(len(IA)):
IA_tmp = IA[ii]
IC_tmp = np.where(IC == IC[IA_tmp])[0]# There are two output
axis_out[ii,:] = axis_in[IA_tmp,:]
coef_out[ii,0] = sum(coef_in[tuple(IC_tmp),0])# Need to convert to tuple
if self.simp == 'simplify':
coef_out = sym.simplify(sym.nsimplify(sym.expand(coef_out), rational=True)) # This line will be good for dephase().
elif self.simp == 'TR8':
coef_out = TR8(sym.nsimplify(sym.expand(coef_out), rational=True))
elif self.simp == 'fu':
coef_out = sym.fu(sym.nsimplify(sym.expand(coef_out), rational=True))
# elif self.simp == 'mix':
# coef_out_simp = sym.simplify(sym.nsimplify(sym.expand(coef_out), rational=True))
# coef_out_TR8 = TR8(sym.nsimplify(sym.expand(coef_out), rational=True))
# for ii in range(len(coef_out)):
# if len(str(coef_out_simp[ii,0])) >= len(str(coef_out_TR8[ii,0])):
# coef_out[ii,0] = coef_out_TR8[ii,0]
# else:
# coef_out[ii,0] = coef_out_simp[ii,0]
# coef_out = coef_out.as_immutable() # If no 'as_immutable()', the "'NoneType' object is not subscriptable" error occurs.
len_coef_out = len(coef_out)
ii_int = 0
# Remove terms with coef = 0
for ii in range(len_coef_out - 1, -1, -1):
if isinstance(coef_out[ii], sym.core.numbers.Zero):
axis_out = np.delete(axis_out, ii, axis=0)
coef_out = coef_out.row_del(ii)
ii_int += 1
# All zero condition.
if ii_int == len_coef_out:
axis_out = np.matrix([0]*self.spin_no)
coef_out = sym.Matrix([0])
return PO(self.spin_no, self.spin_label, axis_out, coef_out, self.basis)
def pulse(self, sp_cell, ph_cell, q_cell):
# Calculation of the change of rho under simultaneous pulses.
# sp_cell: type of spins in a list.
# Character of spin ('I' or 'S' etc.) or
# the order number of spin (0 for 'I', 1 for 'S' etc.). # 0-based index
# (1 for 'I', 2 for 'S' etc.). # 1-based index
# ph_cell: phases of the pulses in a list.
# Character ('x','X','-x' etc.) or number (0,1,2,3)
# quadrature phases are only accepted in this function.
# q_cell: flip angles (rad) in a list (symbolic or double)
# Exmaple:
# rho_new = pulse(rho,['I', 'S'],['x', 'y'],[pi/2, pi/2])
spin_label_cell = self.spin_label
id_vec, ii_vec = PO.sp2id(sp_cell, spin_label_cell)
for ii in range(len(id_vec)):
sp = id_vec[ii]
ph = ph_cell[ii_vec[ii]]
q = q_cell[ii_vec[ii]]
self = PO.pulse1(self, sp, ph, q)[0]
return self
def pulse1(self, sp, ph, q):
# Calculation of the change of rho under a pulse.
# sp: type of spin, character of spin ('I' or 'S' etc.) or
# the order number of spin (0 for 'I', 1 for 'S' etc.). # 0-based index
# ph: phase of the pulse, character ('x','X','-x' etc.) or number (0,1,2,3)
# quadrature phase only.
# q: flip angle in radian (symbolic or double)
basis_org = self.basis
spin_no = self.spin_no
spin_label_cell = self.spin_label
s0 = self.logs
disp0 = self.disp
self = PO.set_basis(self,'xyz')
axis_tmp = np.matrix([0]*spin_no)
if isinstance(sp, str):
for ii in range(len(spin_label_cell)):
if sp == spin_label_cell[ii]:
id_sp = ii # 0-based index
elif isinstance(sp, int):
id_sp = sp # 0-based index
if ph == 'x' or ph == 'X' or (isinstance(ph, int) and ph == 0):
phase_id = 1
coef_tmp = sym.Matrix([1])
ph_tmp = 'x'
elif ph == 'y' or ph == 'Y' or (isinstance(ph, int) and ph == 1):
phase_id = 2
coef_tmp = sym.Matrix([1])
ph_tmp = 'y'
elif ph == '-x' or ph == '-X' or (isinstance(ph, int) and ph == 2):
phase_id = 1
coef_tmp = sym.Matrix([-1])
ph_tmp = '-x'
elif ph == '-y' or ph == '-Y' or (isinstance(ph, int) and ph == 3):
phase_id = 2
coef_tmp = sym.Matrix([-1])
ph_tmp = '-y'
axis_tmp[0, id_sp] = phase_id
H = PO(self.spin_no, self.spin_label, axis_tmp, coef_tmp, 'xyz')
obj = PO.UrhoUinv_mt(self, H, q)
obj = PO.set_basis(obj, basis_org)
obj.disp = disp0
q_s = str(q)
flag_tmp = q_s.find('pi')
if flag_tmp == -1: # Not including 'pi' in q_s
s_out = 'Pulse: %s %s %s' % (spin_label_cell[id_sp],str(q),ph_tmp)
else:
try:
s_out = 'Pulse: %s%4d%s' % (spin_label_cell[id_sp],round(q/pi*180),ph_tmp)
except:
s_out = 'Pulse: %s %s %s' % (spin_label_cell[id_sp],str(q),ph_tmp)
s1 = '%s' % (s_out)
s2 = ' %s' % (obj.txt)
obj.logs = '%s\n%s\n%s' %(s0, s1, s2)
if obj.disp == 1:
print('%s' % (s1))
print('%s' % (s2))
return obj, id_sp
def pulse_phshift(self, sp_cell, ph_cell, q_cell):
# Calculation of the change of rho under simultaneous pulses with arbitrary phases.
# ph_cell: phases of the pulses in a list.
# Arbitrary phases in radian are allowed.
# rho_new = pulse(rho, ['I', 'S'], [pi/4, pi/4], [pi/2, pi/2])
# rho_new = pulse(rho, ['I', 'S'], [q, q], [pi/2, pi/2])
spin_label_cell = self.spin_label
id_vec, ii_vec = PO.sp2id(sp_cell, spin_label_cell)
for ii in range(len(id_vec)):
sp = id_vec[ii]
ph = ph_cell[ii_vec[ii]]
q = q_cell[ii_vec[ii]]
self = PO.pulse_phshift1(self, sp, ph, q)[0]
return self
def pulse_phshift1(self, sp, ph, q):
# Calculation of the change of rho under a pulse with an arbitrary phase.
# sp: type of spin, character of spin ('I' or 'S' etc.) or
# the order number of spin (0 for 'I', 1 for 'S' etc.). # 0-based index
# ph: Arbitrary phase in radian.
# q: flip angle in radian (symbolic or double)
s0 = self.logs
disp0 = self.disp
spin_label_cell = self.spin_label
self.disp = 0
self = PO.cs1(self, sp, -ph)[0]
self = PO.pulse1(self, sp, 'x', q)[0]
self, id_sp = PO.cs1(self, sp, ph) # id_sp: 0-based
self.disp = disp0
q_s = str(q)
flag_tmp = q_s.find('pi')
if flag_tmp == -1: # Not including 'pi' in q_s
s_out = 'Pulse: %s %s %s' % (spin_label_cell[id_sp], str(q), str(ph))
else:
try:
s_out = 'Pulse: %s%4d %s' % (spin_label_cell[id_sp], round(q/pi*180), str(ph))
except:
s_out = 'Pulse: %s %s %s' % (spin_label_cell[id_sp], str(q), str(ph))
s1 = '%s' % (s_out)
s2 = ' %s' % (self.txt)
self.logs = '%s\n%s\n%s' %(s0, s1, s2)
if self.disp == 1:
print('%s' % (s1))
print('%s' % (s2))
return self, id_sp
def cs(self, sp_cell, q_cell):
spin_label_cell = self.spin_label
id_vec, ii_vec = PO.sp2id(sp_cell, spin_label_cell)
for ii in range(len(id_vec)):
sp = id_vec[ii]# int
q = q_cell[ii_vec[ii]]
self = PO.cs1(self, sp, q)[0] #cs1 outputs two arugements as a tuple
return self
def cs1(self, sp, q):
# obj = cs1(obj,sp,q)
# Calculation of the chemical shift evolution of rho.
# obj: PO class object
# sp: type of spin, character of spin ('I' or 'S' etc.) or
# the order number of spin (0 for 'I', 1 for 'S' etc.).
# q: flip angle (symbolic or double)
basis_org = self.basis
spin_no = self.spin_no
s0 = self.logs
disp0 = self.disp
spin_label_cell = self.spin_label
self = PO.set_basis(self,'xyz')
axis_tmp = np.matrix([0]*spin_no)
if isinstance(sp, str):
for ii in range(len(spin_label_cell)):
if sp == spin_label_cell[ii]:
id_sp = ii
elif isinstance(sp, int):
id_sp = sp # 0-based index
coef_tmp = sym.Matrix([1])
axis_tmp[0, id_sp] = 3
H = PO(self.spin_no, self.spin_label, axis_tmp, coef_tmp, 'xyz')
obj = PO.UrhoUinv_mt(self, H, q)
obj = PO.set_basis(obj, basis_org)
obj.disp = disp0
q_s = str(q)
flag_tmp = q_s.find('pi')
if flag_tmp == -1: # Not including 'pi' in q_s
s_out = 'CS: %s %s' % (spin_label_cell[id_sp],str(q))
else:
try:
s_out = 'CS: %s%4d' % (spin_label_cell[id_sp], round(q/pi*180))
except:
s_out = 'CS: %s %s' % (spin_label_cell[id_sp], str(q))
s1 = '%s' % (s_out)
s2 = ' %s' % (obj.txt)
obj.logs = '%s\n%s\n%s' %(s0, s1, s2)
if obj.disp == 1:
print('%s' % (s1))
print('%s' % (s2))
return obj, id_sp
def jc(self, sp_cell, q_cell):
for ii in range(len(sp_cell)):
sp = sp_cell[ii]
q = q_cell[ii]
self = PO.jc1(self, sp, q)[0] #jc1 outputs two arugements as a tuple
return self
def jc1(self, sp, q):
basis_org = self.basis
spin_no = self.spin_no
spin_label_cell = self.spin_label
s0 = self.logs
disp0 = self.disp
self = PO.set_basis(self,'xyz')
axis_tmp = np.matrix([0]*spin_no)
if isinstance(sp, list): # This condiion means that sp is a list of Int, i.e., sp = [1,2]
sp_tmp = ''
for ii in range(2):
if PO.__index_switch == 1: # 1-based index
id_tmp = sp[ii] - 1 # 1-based index => 0-based index
else:
id_tmp = sp[ii] # 0-based index
axis_tmp[0, id_tmp] = 3
sp_tmp = sp_tmp + spin_label_cell[id_tmp]
id_sp = sp
elif isinstance(sp, str):
id_sp = [0]*2
ii_int = -1
for ii in range(len(spin_label_cell)):
spin_label_tmp = spin_label_cell[ii]
if sp.find(spin_label_tmp) >= 0:
id_tmp = ii
axis_tmp[0, id_tmp] = 3
ii_int += 1
id_sp[ii_int] = id_tmp
sp_tmp = sp
coef_tmp = sym.Matrix([1])
H = PO(self.spin_no, self.spin_label, axis_tmp, coef_tmp, 'xyz')
obj = PO.UrhoUinv_mt(self, H, q)
obj = PO.set_basis(obj, basis_org)
obj.disp = disp0
q_s = str(q)
flag_tmp = q_s.find('pi')
if flag_tmp == -1: # Not including 'pi' in q_s
s_out = 'JC: %s %s' % (sp_tmp,str(q))
else:
try:
s_out = 'JC: %s%4d' % (sp_tmp,round(q/pi*180))
except:
s_out = 'JC: %s %s' % (sp_tmp,str(q))
s1 = '%s' % (s_out)
s2 = ' %s' % (obj.txt)
obj.logs = '%s\n%s\n%s' %(s0, s1, s2)
if obj.disp == 1:
print('%s' % (s1))
print('%s' % (s2))
return obj, id_sp
def sp2id(sp_cell, spin_label_cell):
for ii in range(len(sp_cell)):
sp = sp_cell[ii]
if isinstance(sp, int):
if PO.__index_switch == 1:
id_vec_tmp = np.array([sp - 1])# 1-based index => 0-based index
else:
id_vec_tmp = np.array([sp]) # 0-based index
elif isinstance(sp, str):
if sp.find('*') == 0 or sp.find('*') == 1:# Wildcard 'I*' or '*'
if len(sp) == 1: # '*'
id_vec_tmp = np.arange(0,len(spin_label_cell))
elif len(sp) == 2: # 'I*'
# https://stackoverflow.com/questions/14849293/find-all-index-position-in-list-based-on-partial-string-inside-item-in-list
id_vec_tmp = [i for i, s in enumerate(spin_label_cell) if sp[0] in s]
else:
id_vec_tmp = [i for i, s in enumerate(spin_label_cell) if sp in s]
ii_vec_tmp = [ii]*len(id_vec_tmp)
if ii == 0:
id_vec = id_vec_tmp
ii_vec = ii_vec_tmp
else:
id_vec = np.concatenate((id_vec, id_vec_tmp),0)
ii_vec = np.concatenate((ii_vec, ii_vec_tmp),0)
id_vec, id_tmp, tmp = np.unique(id_vec, return_index=True, return_inverse=True, axis=0)
id_vec = id_vec.astype(int).tolist() # Convert elements to int => list, 0-based index
ii_vec = np.array(ii_vec)
ii_vec = ii_vec[id_tmp]
return id_vec, ii_vec
def pfg(self, G_coef, gamma_cell, *args):
# obj = pfg(obj,G_coef,gamma_cell, G_ch)
# applys pulse field gradient to all spins.
# G_coef is a relative strengh of the gradient field and
# gamma_cell a cell array including gyromagnetic ratio of the spins.
# G_ch is used to generate a symbolic constant. E.g., if G_ch is 'x', the symbolic constant is 'Gx'.
# If G_ch is not assigined, 'Z' is automatically used and 'GZ' will be generated.
# This method was obitaned from POMA by Gunter (2006).
if len(args) == 0:
G_ch = 'Z'
elif len(args) == 1:
G_ch = args[0]
GZ = symbols('G'+ G_ch)
obj_tmp = self
spin_label_cell = obj_tmp.spin_label
disp0 = obj_tmp.disp
q_tmp = sym.Matrix([0])
for ii in range(self.spin_no):
s0 = obj_tmp.logs
sp_tmp = spin_label_cell[ii]
q = G_coef*GZ*gamma_cell[ii]
q0 = obj_tmp.PFGq # Initial PFGq
obj_tmp.disp = 0
obj_tmp = PO.cs1(obj_tmp, sp_tmp, q)[0] # PFGq of the output obj_tmp is reset to sym.Matrix([0]).
obj_tmp.disp = disp0
q_s = str(q)
flag_tmp = q_s.find('pi')
if flag_tmp == -1: # Not including 'pi' in q_s
s_out = 'PFG: %s %s' % (spin_label_cell[ii],str(q))
else:
s_out = 'PFG: %s%4d' % (spin_label_cell[ii],round(q/pi*180))
s1 = '%s' % (s_out)
s2 = ' %s' % (obj_tmp.txt)
obj_tmp.logs = '%s\n%s\n%s' %(s0, s1, s2)
if obj_tmp.disp == 1:
print('%s' % (s1))
print('%s' % (s2))
if q0 == sym.Matrix([0]): # Initial condition
# obj_tmp.PFGq[0] = q
obj_tmp.PFGq[0] = GZ*gamma_cell[ii] # Ignore G_coef
else:
# q_tmp[0] = q
# obj_tmp.PFGq = q0.row_join(q_tmp)
q_tmp[0] = GZ*gamma_cell[ii] # Ignore G_coef
obj_tmp.PFGq = q0.row_join(q_tmp)
return obj_tmp
def dephase(self):
# obj = dephase(obj)
# delete terms affected by pfg().
# This method was obitaned from POMA by Gunter (2006).
obj = copy.deepcopy(self)# Give a differnt ID to obj from self
PFGq_in = obj.PFGq
coef_in = obj.coef
s0 = obj.logs
Zpfg = symbols('Zpfg')
if PFGq_in != sym.Matrix([0]):
for ii in range(len(PFGq_in)):
PFGq_tmp = PFGq_in[ii]
for jj in range(len(coef_in)):
# Replace PFGq_tmp to Zpfg
if self.simp == 'simplify':
coef_in[jj] = coef_in[jj].simplify()
elif self.simp == 'TR8':
coef_in[jj] = TR8(coef_in[jj])
elif self.simp == 'fu':
coef_in[jj] = coef_in[jj].fu()
# elif self.simp == 'mix':
# coef_in_simp = coef_in[jj].simplify()
# coef_in_TR8 = TR8(coef_in[jj])
# if len(str(coef_in_simp)) >= len(str(coef_in_TR8)):
# coef_in[jj] = coef_in_TR8
# else:
# coef_in[jj] = coef_in_simp
coef_in[jj] = coef_in[jj].subs(PFGq_tmp, Zpfg)
# https://docs.sympy.org/latest/modules/core.html
# 2.1. Pattern -> expr
a, b = map(Wild, 'ab')
for ii in range(len(coef_in)):
coef_in[ii] = coef_in[ii].rewrite(exp).expand() # This line is important to choose exp(I*a*Zpfg) correctly
coef_in[ii] = coef_in[ii].replace(exp(a*Zpfg),0) # exp(I*a*Zpfg) => 0
coef_in[ii] = coef_in[ii].replace(exp(a*Zpfg + b),0) # exp(I*a*Zpfg + b) => 0
if self.simp == 'simplify':
coef_in[ii] = coef_in[ii].rewrite(cos).simplify() # Conversion from exp to cos and sin
elif self.simp == 'TR8':
coef_in[ii] = TR8(coef_in[ii].rewrite(cos)) # Conversion from exp to cos and sin
elif self.simp == 'fu':
coef_in[ii] = coef_in[ii].rewrite(cos).fu() # Conversion from exp to cos and sin
# elif self.simp == 'mix':
# coef_in_simp = coef_in[ii].rewrite(cos).simplify()
# coef_in_TR8 = TR8(coef_in[ii].rewrite(cos))
# if len(str(coef_in_simp)) >= len(str(coef_in_TR8)):
# coef_in[ii] = coef_in_TR8
# else:
# coef_in[ii] = coef_in_simp
# coef_in = coef_in.as_immutable()
obj.coef = coef_in
obj_out = PO.CombPO(obj) #PFGq is reset to 0
# obj_out.PFGq = PFGq_in # No need to keep the record of PFGq_in
s_out = 'Dephasing of terms including %s' % (str(PFGq_in)[8:-2]) # Matrix([[GZ*gH, GZ*gC]]) => [GZ*gH, GZ*gC]
s1 = '%s' % (s_out)
s2 = ' %s' % (obj_out.txt)
obj_out.logs = '%s\n%s\n%s' %(s0, s1, s2)
if obj_out.disp == 1:
print('%s' % (s1))
print('%s' % (s2))
return obj_out
def UrhoUinv_mt(self, H, q):
if not(isinstance(self, PO)) or not(isinstance(H, PO)):
sys.exit('Both obj and H should be the PO object!')
if H.basis != 'xyz':
sys.exit('The basis of H should be xyz!')
if H.axis.shape[0] > 1:
sys.exit('H must be a single term!')
basis_org = self.basis
self = PO.set_basis(self,'xyz')
mt = np.matrix([[0, 3, -2],[-3, 0, 1],[2, -1, 0]])
mt_large = np.matrix([[0, 3, -2, 1],[-3, 0, 1, 2],[2, -1, 0, 3],[1, 2, 3, 0]])
q = q*H.coef # Matrix
q = q[0,0]# Matrix to a single symbolic value
H_axis = np.array(H.axis)
type_mask_mat = (np.multiply(self.axis, H_axis) != 0)*1 # [True, False] => [0, 1]
axis_diff_mat = (self.axis != H_axis)*1 # [True, False] => [0, 1]
axis_mask_mat = np.multiply(type_mask_mat, axis_diff_mat)
axis_mask_vec = axis_mask_mat.sum(axis=1)
# Python Assigment
# a = [1, 2, 3, 4]
# b = a
# b[0] = 'X'
# Then not only b but also a become
# ['X', 2, 3, 4]
axis_mask_vec2 = axis_mask_vec
axis_mask_vec2[axis_mask_vec2 != 1] = 0
axis_new_tmp = np.zeros((self.axis.shape[0] + axis_mask_vec2.sum(axis=0)[0,0], self.axis.shape[1]))
coef_new_tmp = sym.Matrix(np.zeros((self.axis.shape[0] + axis_mask_vec2.sum(axis=0)[0,0], 1)))
axis_mask_vec3 = axis_mask_vec2 # n x 1 matrix
id_tmp_vec = np.where(axis_mask_vec3 == 1)[0] # Info of row, array
# Python Index
# MATLAB => Python
# a(1:3,5:9) => a[0:3, 4:9]
# a(1:5,:) => a[0:5] or a[:5] or a[0:5, :]
# a(3:2:21,:) => a[2:21:2,:]
for ii in range(len(id_tmp_vec) - 1, -1, -1):
id_tmp = id_tmp_vec[ii]
if id_tmp != len(axis_mask_vec3) - 1:
axis_mask_vec3 = np.concatenate((np.concatenate((axis_mask_vec3[0:id_tmp + 1], np.matrix([2])),axis=0), axis_mask_vec3[id_tmp + 1:len(axis_mask_vec3)]),axis=0)
elif id_tmp == len(axis_mask_vec3) - 1:
axis_mask_vec3 = np.concatenate((axis_mask_vec3[0:id_tmp + 1], np.matrix([2])),axis=0)
# Non-sin terms
axis_new_tmp[np.where(axis_mask_vec3 != 2)[0],:] = self.axis
arr = np.where(axis_mask_vec3 != 2)[0]
id_tmp = arr.tolist()
ii_int = 0
for ii in id_tmp:
coef_new_tmp[ii,0] = self.coef[ii_int]
# ii_int = ii_int + 1
ii_int += 1
# sin terms
arr1 = np.where(axis_mask_vec3 == 1)[0]
id_tmp1 = arr1.tolist()
arr2 = np.where(axis_mask_vec3 == 2)[0]
id_tmp2 = arr2.tolist()
# print(coef_new_tmp[id_tmp, 0]) # This works.
# coef_new_tmp[id_tmp2,0] = coef_new_tmp[id_tmp1,0] # This doesn't work
for ii in range(len(id_tmp1)):
coef_new_tmp[id_tmp2[ii],0] = coef_new_tmp[id_tmp1[ii],0]
# cos terms
axis_cos = axis_new_tmp[np.where(axis_mask_vec3 == 1)[0],:]
for ii in range(len(id_tmp1)):
coef_new_tmp[id_tmp1[ii],0] = cos(q)*coef_new_tmp[id_tmp1[ii],0]
H_axis_mat = np.repeat(H_axis, axis_cos.shape[0], axis=0)
axis_cos4 = axis_cos
H_axis_mat4 = H_axis_mat
axis_cos4[axis_cos4 == 0] = 4
H_axis_mat4[H_axis_mat4 == 0] = 4
axis_sin = np.zeros((axis_cos4.shape[0], axis_cos4.shape[1]))
for ii in range(axis_sin.shape[0]):
id1 = H_axis_mat4[ii,:]
id1 = id1.astype(np.int64)
id2 = axis_cos4[ii,:]
id2 = id2.astype(np.int64)
axis_sin_tmp = mt_large[np.ix_(id1 - 1, id2 - 1)]
axis_sin[ii,:] = np.diag(axis_sin_tmp)
axis_new_tmp[np.where(axis_mask_vec3 == 2)[0],:] = axis_sin
axis_new_tmp = abs(axis_new_tmp)
# It looks that it is not necessary to add ~isempty(axis_sin) necessary.
axis_sin_sign = axis_sin
axis_sin_sign[axis_sin_sign == 0] = 1
axis_sin_sign = np.prod(np.sign(axis_sin_sign),axis=1)
arr2 = np.where(axis_mask_vec3 == 2)[0]
id_tmp2 = arr2.tolist()
for ii in range(len(id_tmp2)):
coef_new_tmp[id_tmp2[ii],0] = sin(q)*coef_new_tmp[id_tmp2[ii],0]*axis_sin_sign[ii]
axis_new_tmp = np.matrix(axis_new_tmp) # if axis_new_tmp is array, it causes an error in getM
obj_out = PO(self.spin_no, self.spin_label, axis_new_tmp, coef_new_tmp, 'xyz') # basis should be 'xyz'
obj_out = PO.CombPO(obj_out)
obj_out = PO.set_basis(obj_out, basis_org)
return obj_out
def xyz2pmz(self):
# conversion from Cartesian operator basis to lowring/raising operator basis
if self.basis != 'xyz':
sys.exit('The basis of the object should be xyz')
axis_in = self.axis
coef_in = self.coef
Ncoef_in = self.Ncoef
spin_no = self.spin_no
disp0 = self.disp
for ii in range(axis_in.shape[0]):
axis_tmp = axis_in[ii,:]# np.matrix
if len(np.where(axis_tmp == 1)[0]) == 0 and len(np.where(axis_tmp == 2)[0]) == 0:
# Only *z operators Iz, 2IzSz, 4IzSzKz,...
axis_out_tmp = axis_tmp
coef_out_tmp = sym.Matrix([1])
else:
# Conversion from Ix and Iy to Ip and Im
xn = len(np.where(axis_tmp == 1)[0]) # Example: IxSyKxMz =>(Ip + Im)(Sp - Sm)(Kp + Km)Mz
yn = len(np.where(axis_tmp == 2)[0]) # xn =2, yn = 1 => 2^(2+1) = 8 terms.
xyn = 2 ** (xn + yn)
axis_out_tmp = np.tile(axis_tmp, (xyn, 1)) # repmat([1 2 1 3],8,1)
axis_out_tmp[axis_out_tmp == 1] = 0 # Remove x operators
axis_out_tmp[axis_out_tmp == 2] = 0 # Remove y operators => [0 0 0 3; 0 0 0 3;... 0 0 0 3]
coef_out_tmp = sym.Matrix([1]*xyn)
dec = np.r_[0:xyn]# 0, 1, ..., xyn
bin_mat = PO.de2bi(dec, (xn + yn)) # np.array, 2D
bin_mat[bin_mat == 0] = 4
bin_mat[bin_mat == 1] = 5
bin_mat = np.matrix(bin_mat) # np.matrix, 2D
int_count = -1
for jj in range(spin_no):
axis_v = axis_tmp[0,jj]
if axis_v == 1 or axis_v == 2:
# int_count = int_count + 1
int_count += 1
bin_vec = bin_mat[:,int_count] # np.matrix, 2D
axis_out_tmp[:,jj] = bin_vec
bin_vec = np.array(bin_vec).transpose()[0] # np.array, 1 x n, 1D
c_tmp = np.array([complex(1 + 0*1j)]*bin_vec.shape[0])
if axis_v == 1: # Ix = 1/2*Ip + 1/2*Im
c_tmp[bin_vec == 4] = 1/2
c_tmp[bin_vec == 5] = 1/2
elif axis_v == 2: # Iy = 1/(2i)*Ip - 1/(2i)*Im
c_tmp[bin_vec == 4] = 1/(2*1j)
c_tmp[bin_vec == 5] = -1/(2*1j)
for kk in range(len(c_tmp)):
coef_out_tmp[kk] = coef_out_tmp[kk]*c_tmp[kk]
# coef_out_tmp = np.multiply(coef_out_tmp,c_tmp[0,:])
coef_out_tmp = coef_out_tmp*coef_in[ii]*Ncoef_in[ii] # Ncoef => coef
if ii == 0:
axis_out = axis_out_tmp
coef_out = coef_out_tmp
else:
axis_out = np.concatenate((axis_out, axis_out_tmp),axis=0)
coef_out = np.concatenate((coef_out, coef_out_tmp),axis=0) # Change to col_join?
axis_out = np.matrix(axis_out)
coef_out = sym.Matrix(coef_out)
obj_out = PO(self.spin_no, self.spin_label, axis_out, coef_out, 'pmz')
obj_out = PO.CombPO(obj_out)
obj_out.disp = disp0
obj_out.logs = '%s\n%s' % (self.logs, obj_out.txt) # Original use char.
return obj_out
def pmz2xyz(self):
# conversion from Cartesian operator basis to lowring/raising operator basis
if self.basis != 'pmz':
sys.exit('The basis of the object should be pmz')
axis_in = self.axis
coef_in = self.coef
Ncoef_in = self.Ncoef
spin_no = self.spin_no
disp0 = self.disp
for ii in range(axis_in.shape[0]):
axis_tmp = axis_in[ii,:]# np.matrix
if len(np.where(axis_tmp == 4)[0]) == 0 and len(np.where(axis_tmp == 5)[0]) == 0:
# Only *z operators Iz, 2IzSz, 4IzSzKz,...
axis_out_tmp = axis_tmp
coef_out_tmp = sym.Matrix([1])
xn = 0
yn = 0
zn = len(np.where(axis_tmp == 3)[0])
else:
# Conversion from Ip and Im to Ix and Iy
xn = len(np.where(axis_tmp == 4)[0]) # p
yn = len(np.where(axis_tmp == 5)[0]) # m
zn = len(np.where(axis_tmp == 3)[0]) # m
xyn = 2 ** (xn + yn)
axis_out_tmp = np.tile(axis_tmp, (xyn, 1))
axis_out_tmp[axis_out_tmp == 4] = 0 # Remove Ip
axis_out_tmp[axis_out_tmp == 5] = 0 # Remove Im
coef_out_tmp = sym.Matrix([1]*xyn)
dec = np.r_[0:xyn]# 0, 1, ..., xyn
bin_mat = PO.de2bi(dec, (xn + yn)) # np.array, 2D
bin_mat[bin_mat == 0] = 2
bin_mat[bin_mat == 1] = 1
bin_mat = np.matrix(bin_mat) # np.matrix, 2D
int_count = -1
for jj in range(spin_no):
axis_v = axis_tmp[0,jj]
if axis_v == 4 or axis_v == 5:
# int_count = int_count + 1
int_count += 1