-
Notifications
You must be signed in to change notification settings - Fork 0
/
time_evolution.py
1204 lines (1021 loc) · 49.6 KB
/
time_evolution.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
from __future__ import annotations
from typing import List
import numpy as np
import scipy
import scipy.sparse.linalg
import scipy.optimize
from pyjjasim.josephson_circuit import Circuit
from pyjjasim.static_problem import DefaultCPR
from pyjjasim.static_problem import StaticConfiguration, StaticProblem
__all__ = ["TimeEvolutionProblem", "TimeEvolutionResult", "AnnealingProblem"]
"""
Time Evolution Module
"""
class TimeEvolutionProblem:
"""
Define multiple time evolution problems with varying parameters in a Josephson circuit.
- All problems are computed in one call (ofter faster than computing one by one).
- The problem count is abbreviated with the symbol W.
- Use self.compute() to obtain a TimeEvolutionResult object containing the
resulting time evolution.
- Be careful input arrays for Is, f, etc. have the correct shape. See numpy
broadcasting for details https://numpy.org/doc/stable/user/basics.broadcasting.html
Remember Nj is number of junctions, Nn is number of nodes and Nf is number of faces.
Parameters
----------
circuit : Circuit
Josephson circuit on which the problem is based.
time_step=0.05 :
Time between consecutive steps (abbreviated dt).
time_step_count=1000 : Nt
Total number of time steps in evolution (abbreviated Nt).
current_phase_relation= DefaultCPR()
Current-phase relation (abbreviated cp).
external_flux=0.0 : array broadcastable to (Nf, W, Nt).
Normalized external flux per site, called external_flux (abbreviated f)
external_flux (alternative) : f(i) -> array broadcastable to (Nf, W) for i in range(Nt)
Alternative input type for external_flux using a function.
current_sources : array broadcastable to (Nj, W, Nt)
Current source strength at each junction in circuit (abbreviated Is).
current_sources (alternative) : Is(i) -> array broadcastable to (Nj, W) for i in range(Nt)
Alternative input type for current_sources using a function.
voltage_sources=0.0 : array broadcastable to (Nj, W, Nt)
Voltage source value at each junction in circuit (abbreviated Vs).
voltage_sources (alternative) : Vs(i) -> array broadcastable to (Nj, W) for i in range(Nt)
Alternative input type for voltage_sources using a function.
temperature=0.0 : array broadcastable to (Nj, W, Nt)
Temperature at each junction in circuit (abbreviated T).
temperature (alternative) : T(i) -> array broadcastable to (Nj, W) for i in range(Nt)
Alternative input type for temperature using a function.
store_time_steps=None : array in range(Nt), mask of shape (Nt,) or None
Indicate at which timesteps data is stored in the output array(s).
store_theta=True : bool
If true, theta (the gauge invarian t phase difference) is stored during
a time evolution (at timesteps specified with store_time_steps) and
returned when simulation is done.
store_voltage=True : bool
If true, voltage is stored during a time evolution (at timesteps specified
with store_time_steps) and returned when simulation is done.
store_current=True : bool
If true, voltage is stored during a time evolution (at timesteps specified
with store_time_steps) and returned when simulation is done.
config_at_minus_1=None : (Nj, W) array or StaticConfiguration or None
initial condition at timestep=-1. set to self.get_static_problem(t=0,n=z).compute()
config_at_minus_2=None : (Nj, W) array or StaticConfiguration or None
initial condition at timestep=-2
Notes
-----
- All physical quantities are dimensionless. See the UserManual (on github)
for how all quantities are normalized.
- It is assumed each junction has a current source and voltage source see
user manual (on github) for diagram of junction. To omit the sources in
particular junctions set the respective values to zero.
- To use a node-based source current (represented as an (Nn,) array Is_node
with current in/e-jected at each node), convert it to a junction-based
source with Is = static_problem.node_to_junction_current(circuit, Is_node)
and use Is as input for a time evolution.
- The store parameters allow one to control what quantities are stored at what timesteps.
Only the base quantities theta, voltage and current can be stored in the resulting
TimeEvolutionResult; other quantities like energy or magnetic_flux are computed based
on these. See documentation of `TimeEvolutionResult` to see per quantity which
base quantities are required.
"""
def __init__(self, circuit: Circuit, time_step=0.05, time_step_count=1000,
current_phase_relation=DefaultCPR(),
external_flux=0.0, current_sources=0.0,
voltage_sources=0.0, temperature=0.0,
store_time_steps=None, store_theta=True, store_voltage=True, store_current=True,
config_at_minus_1: np.ndarray = None,
config_at_minus_2: np.ndarray = None,
config_at_minus_3: np.ndarray = None,
config_at_minus_4: np.ndarray = None,
stencil_width=3):
self.circuit = circuit
self.time_step = time_step
self.time_step_count = time_step_count
self.current_phase_relation = current_phase_relation
def get_prob_cnt(x):
s = np.array(x(0) if hasattr(x, "__call__") else x).shape
return s[1] if len(s) > 1 else 1
self.problem_count = max(get_prob_cnt(external_flux),
get_prob_cnt(current_sources), get_prob_cnt(voltage_sources),
get_prob_cnt(temperature))
Nj, Nf, W, Nt = self.circuit._Nj(), self.circuit._Nf(), self.get_problem_count(), self.get_time_step_count()
self._f_is_timedep = TimeEvolutionProblem._is_timedep(external_flux)
self.external_flux = external_flux if hasattr(external_flux, "__call__") else \
np.broadcast_to(np.array(external_flux), (Nf, W, Nt))
self._Is_is_timedep = TimeEvolutionProblem._is_timedep(current_sources)
self.current_sources = current_sources if hasattr(current_sources, "__call__") else \
np.broadcast_to(np.array(current_sources), (Nj, W, Nt))
self._Vs_is_timedep = TimeEvolutionProblem._is_timedep(voltage_sources)
self.voltage_sources = voltage_sources if hasattr(voltage_sources, "__call__") else \
np.broadcast_to(np.array(voltage_sources), (Nj, W, Nt))
self._T_is_timedep = TimeEvolutionProblem._is_timedep(temperature)
self.temperature = temperature if hasattr(temperature, "__call__") else \
np.broadcast_to(np.array(temperature), (Nj, W, Nt))
self.store_time_steps = np.ones(self._Nt(), dtype=bool)
self.store_time_steps = self._to_time_point_mask(store_time_steps)
self.store_theta = store_theta
self.store_voltage = store_voltage
self.store_current = store_current
if not (self.store_theta or self.store_voltage or self.store_current):
raise ValueError("No output is stored")
if np.sum(self.store_time_steps) == 0:
raise ValueError("No output is stored")
self.stencil_width = stencil_width
self.stencil = self._get_stencil(self.stencil_width)
self.config_at_minus_1 = self._get_config(config_at_minus_1, np.zeros((Nj, W), dtype=np.double), (Nj, W))
self.config_at_minus_2 = self._get_config(config_at_minus_2, self.config_at_minus_1, (Nj, W))
if self.stencil_width >= 4:
self.config_at_minus_3 = self._get_config(config_at_minus_3, self.config_at_minus_2, (Nj, W))
if self.stencil_width >= 5:
self.config_at_minus_4 = self._get_config(config_at_minus_4, self.config_at_minus_3, (Nj, W))
def get_static_problem(self, vortex_configuration, problem_nr=0, time_step=0) -> StaticProblem:
"""
Return a static problem with properties copied from this time evolution.
Parameters
----------
vortex_configuration : (Nf,) array or None
Vortex configuration of returned static problem.
problem_nr=0 : int in range(W)
Selected problem number to copy properties of.
time_step=0 : int in range(Nt)
Selected timestep to copy properties of.
Returns
-------
static_problem : StaticProblem
Static problem where the parameters are copied from this time evolution.
"""
return StaticProblem(self.circuit, current_sources=self._Is(time_step)[:, problem_nr].copy(),
external_flux=self._f(time_step)[:, problem_nr].copy(),
vortex_configuration=vortex_configuration,
current_phase_relation=self.current_phase_relation)
def get_problem_count(self):
"""
Return number of problems (abbreviated W).
"""
return self.problem_count
def get_circuit(self) -> Circuit:
"""
Returns the circuit.
"""
return self.circuit
def get_time_step(self):
"""
Return the timestep (abbreviated dt).
"""
return self.time_step
def get_time_step_count(self):
"""
Return the number of timesteps (abbreviated Nt).
"""
return self.time_step_count
def get_current_phase_relation(self):
"""
Returns the current-phase relation.
"""
return self.current_phase_relation
def get_phase_zone(self):
"""
Returns the phase zone (In all of pyJJAsim phase_zone=0).
"""
return 0
def get_external_flux(self):
"""
Returns the external_flux (abbreviated f).
"""
return self.external_flux
def get_current_sources(self):
"""
Returns the current sources (abbreviated Is).
"""
return self.current_sources
def get_net_sourced_current(self, time_step):
"""
Gets the sum of all (positive) current injected at nodes to create Is.
Parameters
----------
time_step : int
Time step at which to return net sourced current.
Returns
-------
net_sourced_current : (W,) array
Net sourced current through array for each problem at specified timestep.
"""
M = self.get_circuit().get_cut_matrix()
return 0.5 * np.sum(np.abs((M @ self._Is(time_step))), axis=0)
def get_node_current_sources(self, time_step):
"""
Returns (Nn,) array of currents injected at nodes to create Is.
Parameters
----------
time_step : int
Time step at which to return node currents.
Returns
-------
node_current : (Nn, W) array
Node currents for each problem at specified timestep.
"""
M = self.get_circuit().get_cut_matrix()
return M @ self._Is(time_step)
def get_voltage_sources(self):
"""
Return voltage sources at each junction (abbreviated Vs)
"""
return self.voltage_sources
def get_temperature(self):
"""
Return temperature at each junction (abbreviated T)
"""
return self.temperature
def get_store_time_steps(self):
"""
Return at which timesteps data is stored in the output array(s).
config_at_minus_1=None : (Nj, W) array or StaticConfiguration or None
initial condition at timestep=-1. set to self.get_static_problem(t=0,n=z).compute()
config_at_minus_2=None : (Nj, W) array or StaticConfiguration or None
initial condition at timestep=-2
"""
return self.store_time_steps
def get_store_theta(self):
"""
Return if theta is stored during a time evolution.
"""
return self.store_theta
def get_store_voltage(self):
"""
Return if voltage is stored during a time evolution.
"""
return self.store_voltage
def get_store_current(self):
"""
Return if current is stored during a time evolution.
"""
return self.store_current
def get_time(self):
"""
Return (Nt,) array with time value at each step of evolution.
"""
return np.arange(self._Nt(), dtype=np.double) * self._dt()
def compute(self) -> TimeEvolutionResult:
"""
Compute time evolution on an Josephson Circuit.
"""
return time_evolution(self)
def __str__(self):
return "time evolution problem: " + \
"\n\ttime: " + self.time_step_count.__str__() + " steps of " + self.time_step.__str__() + \
"\n\tcurrent sources: " + self.current_sources.__str__() + \
"\n\tvoltage sources: " + self.voltage_sources.__str__() + \
"\n\texternal_flux: " + self.external_flux.__str__() + \
"\n\ttemperature: " + self.temperature.__str__() + \
"\n\tcurrent-phase relation: " + self.current_phase_relation.__str__()
def _Nt(self):
return self.time_step_count
def _Nt_s(self):
return np.asscalar(np.sum(self.store_time_steps))
def _dt(self):
return self.time_step
@staticmethod
def _get_config(config_cur, config_prev, shape):
config_cur = config_prev.copy() if config_cur is None else config_cur
if hasattr(config_cur, "get_theta"):
config_cur = config_cur.get_theta()
return config_cur.reshape(shape) # always (Nj, W) shaped
@staticmethod
def _is_timedep(x):
if hasattr(x, "__call__"):
return True
if len(np.array(x).shape) == 0:
return False
return np.array(x).shape[-1] > 1
def _f(self, time_step) -> np.ndarray: # (Nf, W), read-only
return np.broadcast_to(self.external_flux(time_step), (self.circuit._Nf(), self.get_problem_count())) \
if hasattr(self.external_flux, "__call__") else self.external_flux[:, :, time_step]
def _Is(self, time_step) -> np.ndarray: # (Nj, W), read-only
return np.broadcast_to(self.current_sources(time_step), (self.circuit._Nj(), self.get_problem_count()))\
if hasattr(self.current_sources, "__call__") else self.current_sources[:, :, time_step]
def _Vs(self, time_step) -> np.ndarray: # (Nj, W), read-only
return np.broadcast_to(self.voltage_sources(time_step), (self.circuit._Nj(), self.get_problem_count()))\
if hasattr(self.voltage_sources, "__call__") else self.voltage_sources[:, :, time_step]
def _T(self, time_step) -> np.ndarray: # (Nj, W), read-only
return np.broadcast_to(self.temperature(time_step), (self.circuit._Nj(), self.get_problem_count()))\
if hasattr(self.temperature, "__call__") else self.temperature[:, :, time_step]
def _theta_s(self, time_step) -> np.ndarray: # (Nj, W), read-only
# warning: must be called exactly once at every timestep in order, starting below zero.
if time_step < 0:
self.prepared_theta_s = self._Vs(0) * (time_step * self._dt())
else:
self.prepared_theta_s = self.prepared_theta_s + self._Vs(time_step) * self._dt()
return self.prepared_theta_s
def _cp(self, theta) -> np.ndarray: # (Nj, W)
# theta -> (Nj, W)
Ic = self.get_circuit()._Ic()[:, None]
return self.current_phase_relation.eval(Ic, theta)
def _dcp(self, theta) -> np.ndarray: # (Nj, W)
Ic = self.get_circuit()._Ic()[:, None]
return self.current_phase_relation.d_eval(Ic, theta)
def _icp(self, theta) -> np.ndarray: # (Nj, W)
Ic = self.get_circuit()._Ic()[:, None]
return self.current_phase_relation.i_eval(Ic, theta)
def _broadcast(self, x, shape):
x_shape = np.array(x).shape
x = x.reshape(x_shape + (1,) * (len(shape) - len(x_shape)))
return np.broadcast_to(x, shape)
def _to_time_point_mask(self, time_points):
# time_points: None (represents all points)
# or slice
# or array in range(Nt)
# or mask of shape (Nt,)
if time_points is None:
time_points = self.store_time_steps
time_points = np.array(time_points)
if not (time_points.dtype in (bool, np.bool)):
try:
x = np.zeros(self._Nt(), dtype=bool)
x[time_points] = True
time_points = x
except:
raise ValueError("Invalid store_time_steps; must be None, mask, slice or index array")
return time_points
def _get_stencil(self, width : int):
if width == 3:
return (1.0, -1.0, 0.0), (1.0, -2.0, 1.0)
if width == 4:
return (1.5, -2.0, 0.5, 0.0), (2.0, -5.0, 4.0, -1.0)
if width == 5:
return (11.0/6, -3.0, 1.5, -1.0/3, 0.0), (35.0/12, -26.0/3, 9.5, -14.0/3, 11.0/12)
raise ValueError(f"stencil width must be 3, 4 or 5 (equals {width})")
def _apply_derivative(x, index, stencil, dt):
w = len(stencil)
if w == 3:
return (stencil[0] * x[:, :, index] + stencil[1] * x[:, :, index - 1]) / dt
if w == 4:
return (stencil[0] * x[:, :, index] + stencil[1] * x[:, :, index - 1] +
stencil[2] * x[:, :, index - 2]) / dt
if w == 5:
return (stencil[0] * x[:, :, index] + stencil[1] * x[:, :, index - 1] +
stencil[2] * x[:, :, index - 2] + stencil[3] * x[:, :, index - 3]) / dt
def time_evolution(problem: TimeEvolutionProblem):
# determine what timepoints to store. Complicated by the fact that voltage needs both derivative
# of theta and current (if inductance is present) for which extra timepoints must be stored depending
# on the derivative stencil.
th_store_mask = problem.store_time_steps.copy() if (problem.store_theta or problem.store_voltage) else np.zeros(problem._Nt(), dtype=bool)
I_store_mask = problem.store_time_steps.copy() if (problem.store_current or problem.store_voltage) else np.zeros(problem._Nt(), dtype=bool)
V_th_store_mask = th_store_mask.copy()
V_I_store_mask = I_store_mask.copy()
t_ids = np.flatnonzero(problem.store_time_steps)
Nj = problem.circuit.junction_count()
offset = problem.stencil_width - 1 # number of initial conditions used
if problem.store_voltage:
if len(t_ids) > 0:
Vt_ids = (t_ids[:, None] - np.arange(offset)).ravel()
Vt_ids = Vt_ids[(Vt_ids >= 0) & (Vt_ids < problem._Nt())]
V_th_store_mask[Vt_ids] = True
if problem.circuit._has_inductance():
V_I_store_mask[Vt_ids] = True
# th_out will be of shape (Nj, W, offset + sum(V_th_store_mask)). Note that the initial
# conditions before time_point=0 are always stored.
th_out, I_out = time_evolution_core(problem, V_th_store_mask, V_I_store_mask)
if problem.store_voltage:
ts = np.flatnonzero((problem.store_time_steps)[V_th_store_mask])
V_out = _apply_derivative(th_out, index=ts + offset, stencil=problem.stencil[0], dt=problem._dt())
if problem.circuit._has_inductance():
V_ind = _apply_derivative(I_out, index=ts + offset, stencil=problem.stencil[0], dt=problem._dt())
V_out += (problem.circuit.get_inductance() @ V_ind.reshape((Nj, -1))).reshape(V_out.shape)
th_out = np.delete(th_out, np.flatnonzero((V_th_store_mask & ~ th_store_mask)[V_th_store_mask]) + offset, axis=2)
I_out = np.delete(I_out, np.flatnonzero((V_I_store_mask & ~ I_store_mask)[V_I_store_mask]) + offset, axis=2)
th_out = th_out[:, :, offset:] # remove initial conditions -> shape=(Nj, W, sum(th_store_mask))
I_out = I_out[:, :, offset:]
return TimeEvolutionResult(problem, th_out if problem.store_theta else None,
I_out if problem.store_current else None,
V_out if problem.store_voltage else None)
def time_evolution_core(problem: TimeEvolutionProblem, th_store_mask, I_store_mask):
"""
Algorithm 2 for time-evolution. Best algo?
"""
circuit = problem.get_circuit()
Nj, W = circuit._Nj(), problem.get_problem_count()
dt = problem._dt()
A = circuit.get_cycle_matrix()
AT = A.T
Rv = 1 / (dt * circuit._R()[:, None])
Cv = circuit._C()[:, None] / (dt ** 2)
C1, C2 = problem.stencil
Cprev, C0, Cnext = Cv, -2.0 * Cv - Rv, Cv + Rv
c0 = C1[0] * Rv + C2[0] * Cv
c1 = C1[1] * Rv + C2[1] * Cv
theta_next = problem.config_at_minus_1.copy()
s_width = problem.stencil_width
th_out = np.zeros((Nj, W, np.sum(th_store_mask) + s_width - 1), dtype=np.double)
I_out = np.zeros((Nj, W, np.sum(I_store_mask) + s_width - 1), dtype=np.double)
th_out[:, :, s_width - 2] = theta_next
I_out[:, :, s_width - 2] = problem._cp(theta_next)
c2 = C1[2] * Rv + C2[2] * Cv
theta1 = problem.config_at_minus_2.copy()
th_out[:, :, s_width - 3] = theta1
I_out[:, :, s_width - 3] = problem._cp(theta1)
if s_width >= 4:
c3 = C1[3] * Rv + C2[3] * Cv
theta2 = problem.config_at_minus_3.copy()
th_out[:, :, s_width - 4] = theta2
I_out[:, :, s_width - 4] = problem._cp(theta2)
if s_width >= 5:
c4 = C1[4] * Rv + C2[4] * Cv
theta3 = problem.config_at_minus_4.copy()
th_out[:, :, s_width - 5] = theta3
I_out[:, :, s_width - 5] = problem._cp(theta3)
L = problem.circuit._L()
A_mat = A @ (L + scipy.sparse.diags(1.0 / Cnext[:, 0], 0)) @ AT
Asq_fact = scipy.sparse.linalg.factorized(A_mat)
theta_s = np.zeros((Nj, W), dtype=np.double)
Is, T, Vs, f = problem._Is(0), problem._T(0), problem._Vs(0), problem._f(0)
Is_zero, T_zero, Vs_zero, f_zero = False, False, False, False
fluctuations = 0.0
if not problem._T_is_timedep:
T_zero = np.allclose(T, 0)
if not problem._Is_is_timedep:
Is_zero = np.allclose(Is, 0)
if not problem._Vs_is_timedep:
Vs_zero = np.allclose(Vs, 0)
if not problem._f_is_timedep:
f_zero = np.allclose(f, 0)
i_th = 0
i_I = 0
for i in range(problem._Nt()):
if problem._T_is_timedep:
T = problem._T(i)
if problem._Is_is_timedep:
Is = problem._Is(i)
if problem._Vs_is_timedep:
Vs = problem._Vs(i)
if problem._f_is_timedep:
f = problem._f(i)
if not T_zero:
if circuit.junction_count() > 500:
rand = np.random.randn(Nj, W) if i % 3 == 0 else rand[np.random.permutation(Nj), :]
else:
rand = np.random.randn(Nj, W)
fluctuations = ((2.0 * T * Rv) ** 0.5) * rand
if s_width >= 5:
theta4 = theta3.copy()
if s_width >= 4:
theta3 = theta2.copy()
theta2 = theta1.copy()
theta1 = theta_next.copy()
if s_width == 3:
X = problem._cp(2 * theta1 - theta2) + c1 * theta1 + c2 * theta2
if s_width == 4:
X = problem._cp(3 * theta1 - 3 * theta2 + theta3) + c1 * theta1 + c2 * theta2 + c3 * theta3
if s_width == 5:
X = problem._cp(4 * theta1 - 6 * theta2 + 4 * theta3 - theta4) + \
c1 * theta1 + c2 * theta2 + c3 * theta3 + c4 * theta4
if Is_zero:
x = fluctuations + X
else:
x = fluctuations - Is + X
if Vs_zero:
if f_zero:
y = AT @ Asq_fact(A @ (x / c0))
else:
y = AT @ Asq_fact(A @ (x / c0) - 2 * np.pi * f)
else:
if f_zero:
y = AT @ Asq_fact(A @ (x / c0 - theta_s))
else:
y = AT @ Asq_fact(A @ (x / c0 - theta_s) - 2 * np.pi * f)
theta_next = (y - x) / c0
if th_store_mask[i]:
th_out[:, :, i_th + s_width - 1] = theta_next
i_th += 1
if I_store_mask[i]:
I_out[:, :, i_I + s_width - 1] = y + Is
i_I += 1
if not Vs_zero:
theta_s += Vs *dt
return th_out, I_out
class ThetaNotStored(Exception):
pass
class CurrentNotStored(Exception):
pass
class VoltageNotStored(Exception):
pass
class DataAtTimepointNotStored(Exception):
pass
class TimeEvolutionResult:
"""
Represents data of simulated time evolution(s) on a Josephson circuit.
One can query several properties of the circuit configurations, like currents
and voltages. TimeEvolutionResult only store theta, current and voltage data
from which all quantities are computed. However, one can choose not to store
all three to save memory. An error is raised if one attempts to compute a property
for which the required data is not stored.
"""
def __init__(self, problem: TimeEvolutionProblem, theta, current, voltage):
self.problem = problem
Nj, W, Nt_s = problem.circuit._Nj(), self.get_problem_count(), problem._Nt_s()
self.theta = theta
self.voltage = voltage
self.current = current
if problem.store_theta:
if self.theta.shape != (Nj, W, Nt_s):
raise ValueError(f"theta must have shape {(Nj, W, Nt_s)}; has shape {self.theta.shape}")
else:
self.theta = None
if problem.store_current:
if self.current.shape != (Nj, W, Nt_s):
raise ValueError(f"current must have shape {(Nj, W, Nt_s)}; has shape {self.current.shape}")
else:
self.current = None
if problem.store_voltage:
if self.voltage.shape != (Nj, W, Nt_s):
raise ValueError(f"voltage must have shape {(Nj, W, Nt_s)}; has shape {self.voltage.shape}")
else:
self.voltage = None
s = self.problem.store_time_steps.astype(int)
self.time_point_indices = np.cumsum(s) - s
self.animation = None
def _th(self, time_point) -> np.ndarray:
if self.theta is None:
raise ThetaNotStored("Cannot query theta; quantity is not stored during time evolution simulation.")
return self.theta[:, :, self._time_point_index(time_point)]
def _V(self, time_point) -> np.ndarray:
if self.voltage is None:
raise VoltageNotStored("Cannot query voltage; quantity is not stored during time evolution simulation.")
return self.voltage[:, :, self._time_point_index(time_point)]
def _I(self, time_point) -> np.ndarray:
if self.current is None:
raise CurrentNotStored("Cannot query current; quantity is not stored during time evolution simulation.")
return self.current[:, :, self._time_point_index(time_point)]
def _time_point_index(self, time_points):
if time_points is None:
time_points = self.problem.store_time_steps
if not np.all(self.problem.store_time_steps[time_points]):
raise DataAtTimepointNotStored("Queried a timepoint that is not stored during time evolution simulation.")
return self.time_point_indices[time_points]
def get_problem_count(self):
"""
Return number of problems (abbreviated W).
"""
return self.problem.get_problem_count()
def get_circuit(self) -> Circuit:
"""
Return Josephson circuit.
"""
return self.problem.get_circuit()
def select_static_configuration(self, prob_nr, time_step) -> StaticConfiguration:
"""
Return a StaticConfiguration object with the data copied from this result for
a given problem number at a given timestep.
Parameters
----------
prob_nr : int
Selected problem.
time_step : int
Selected timestep.
Returns
-------
static_conf : StaticConfiguration
A StaticConfiguration object with the data copied from this result
"""
if self.theta is None:
raise ValueError("Theta not stored; cannot select static configuration.")
problem = StaticProblem(self.get_circuit(), current_sources=self.problem._Is(time_step)[:, prob_nr],
external_flux=self.problem._f(time_step)[:, prob_nr],
vortex_configuration=self.get_vortex_configuration(time_step)[:, prob_nr],
current_phase_relation=self.problem.current_phase_relation)
return StaticConfiguration(problem, self.theta[:, prob_nr, time_step])
def get_phase(self, select_time_points=None) -> np.ndarray:
"""
Return node phases. Last node is grounded. Requires theta to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
phi : (Nn, W, nr_of_selected_timepoints) array
Node phases.
"""
c = self.get_circuit()
M, Nj = c.get_cut_matrix(), c._Nj()
func = lambda tp: c.Msq_solve(M @ self._th(tp).reshape(Nj, -1))
try:
return self._select(select_time_points, self.get_circuit()._Nn(), func)
except ThetaNotStored:
raise ThetaNotStored("Cannot compute phi; requires theta to be stored in TimeEvolutionConfig")
def get_theta(self, select_time_points=None) -> np.ndarray:
"""
Return gauge invariant phase differences.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
theta : (Nj, W, nr_of_selected_timepoints) array
Gauge invariant phase differences.
"""
return self._select(select_time_points, self.get_circuit()._Nj(), self._th)
def get_vortex_configuration(self, select_time_points=None) -> np.ndarray:
"""
Return vorticity at faces. Requires theta to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
n : (Nf, W, nr_of_selected_timepoints) int array
Vorticity.
"""
A = self.get_circuit().get_cycle_matrix()
func = lambda tp: -A @ np.round(self._th(tp) / (2.0 * np.pi))
try:
return self._select(select_time_points, self.get_circuit()._Nf(), func).astype(int)
except ThetaNotStored:
raise ThetaNotStored("Cannot compute n; requires theta to be stored in TimeEvolutionConfig")
def get_josephson_energy(self, select_time_points=None) -> np.ndarray:
"""
Return Josephson energy of junctions. Requires theta to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
EJ : (Nf, W, nr_of_selected_timepoints) array
Josephson energy.
"""
func = lambda tp: self.problem._icp(self._th(tp))
try:
return self._select(select_time_points, self.get_circuit()._Nj(), func)
except ThetaNotStored:
raise ThetaNotStored("Cannot compute Josephson energy EJ; requires theta to be stored in TimeEvolutionConfig")
def get_current(self, select_time_points=None) -> np.ndarray:
"""
Return current through junctions.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
I : (Nj, W, nr_of_selected_timepoints) array
Current.
"""
return self._select(select_time_points, self.get_circuit()._Nj(), self._I)
def get_supercurrent(self, select_time_points=None) -> np.ndarray:
"""
Return supercurrent through junctions. Requires theta to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
Isup : (Nj, W, nr_of_selected_timepoints) array
Supercurrent.
"""
func = lambda tp: self.problem._cp(self._th(tp))
try:
return self._select(select_time_points, self.get_circuit()._Nj(), func)
except ThetaNotStored:
raise ThetaNotStored("Cannot compute supercurrent Isup; requires theta to be stored in TimeEvolutionConfig")
def get_cycle_current(self, select_time_points=None) -> np.ndarray:
"""
Return cycle-current J around faces. Requires current to be stored. Defined
as I = A.T @ J + I_source.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
J : (Nf, W, nr_of_selected_timepoints) array
Cycle-current around faces.
"""
A = self.get_circuit().get_cycle_matrix()
func = lambda tp: self.get_circuit().Asq_solve(A @ (self._I(tp) - self.problem._Is(tp)))
try:
return self._select(select_time_points, self.get_circuit()._Nf(), func)
except CurrentNotStored:
raise CurrentNotStored("Cannot compute cycle-current J; requires current to be stored in TimeEvolutionConfig")
def get_flux(self, select_time_points=None) -> np.ndarray:
"""
Return magnetic flux through faces. Requires current to be stored.
Defined as f + (A @ L @ I) / (2 * pi).
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
flux : (Nf, W, nr_of_selected_timepoints) array
Magnetic flux through faces
"""
Nj, Nf = self.get_circuit()._Nj(), self.get_circuit()._Nf()
A = self.get_circuit().get_cycle_matrix()
func = lambda tp: self.problem._f(tp) + A @ (self.get_circuit()._L() @ self._I(tp)) / (2 * np.pi)
try:
return self._select(select_time_points, Nf, func)
except CurrentNotStored:
raise CurrentNotStored("Cannot compute magnetic flux; requires current to be stored in TimeEvolutionConfig")
def get_magnetic_energy(self, select_time_points=None) -> np.ndarray:
"""
Return magnetic energy associated with wires. Requires current to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
EM : (Nf, W, nr_of_selected_timepoints) array
Magnetic energy associated with wires.
"""
Nj = self.get_circuit()._Nj()
func = lambda tp: 0.5 * self.get_circuit()._L() @ (self._I(tp) ** 2)
try:
is_zero = not self.get_circuit()._has_inductance()
return self._select(select_time_points, Nj, func, is_zero=is_zero)
except CurrentNotStored:
raise CurrentNotStored("Cannot compute magnetic energy EM; requires current to be stored in TimeEvolutionConfig")
def get_voltage(self, select_time_points=None):
"""
Return voltage over junctions.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
V : (Nj, W, nr_of_selected_timepoints) array
Voltage.
"""
return self._select(select_time_points, self.get_circuit()._Nj(), self._V)
def get_potential(self, select_time_points=None):
"""
Return voltage potential at nodes. Last node is groudend.Requires
voltage to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
U : (Nn, W, nr_of_selected_timepoints) array
Voltage potential at nodes.
"""
M, Nj = self.get_circuit().get_cut_matrix(), self.get_circuit()._Nj()
# Mrsq = M @ M.T
# Z = np.zeros((1, self.get_problem_count()), dtype=np.double)
# solver = scipy.sparse.linalg.factorized(Mrsq)
# func = lambda tp: np.concatenate((solver(M @ self._V(tp)), Z), axis=0)
func = lambda tp: self.get_circuit().Msq_solve(M @ self._V(tp).reshape(Nj, -1))
try:
return self._select(select_time_points, self.get_circuit()._Nn(), func)
except VoltageNotStored:
raise VoltageNotStored(
"Cannot compute electric potential U; requires voltage to be stored in TimeEvolutionConfig")
def get_capacitive_energy(self, select_time_points=None):
"""
Return energy stored in capacitors at each junction. Requires voltage
to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
EC : (Nj, W, nr_of_selected_timepoints) array
Energy stored in capacitors
"""
C, Nj = self.get_circuit()._C(), self.get_circuit()._Nj()
func = lambda tp: 0.5 * C[:, None] * self._V(tp) ** 2
try:
is_zero = not self.get_circuit()._has_capacitance()
return self._select(select_time_points, Nj, func, is_zero=is_zero)
except VoltageNotStored:
raise VoltageNotStored(
"Cannot compute capacitive energy EC; requires voltage to be stored in TimeEvolutionConfig")
def get_energy(self, select_time_points=None) -> np.ndarray:
"""
Return total energy associated with each junction. Requires theta,
current and voltage to be stored.
Parameters
----------
select_time_points=None : array in range(Nt), (Nt,) mask or None
Selected time_points at which to return data. If None, all stored
timepoints are returned. Raises error if no data is available at
requested timepoint.
Returns
-------
Etot : (Nj, W, nr_of_selected_timepoints) array
Total energy associated with each junction.
"""
return self.get_josephson_energy(select_time_points) + self.get_magnetic_energy(select_time_points) + \
self.get_capacitive_energy(select_time_points)
def plot(self, problem_nr=0, time_point=0, fig=None, node_quantity=None,
junction_quantity="I", face_quantity=None, vortex_quantity="n",
show_grid=True, show_nodes=True, return_plot_handle=False, **kwargs):
"""
Visualize a problem at a specified timestep.
See :py:attr:`circuit_visualize.CircuitPlot` for documentation.
Attributes
----------
return_plot_handle=False : bool
If True this method returns the ConfigPlot object used to create the plot.
Returns
-------
fig : matplotlib figure handle