fiqus 2025.2.0__py3-none-any.whl → 2025.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fiqus/MainFiQuS.py +4 -9
- fiqus/data/DataConductor.py +350 -301
- fiqus/data/DataFiQuS.py +42 -115
- fiqus/data/DataFiQuSCCT.py +150 -150
- fiqus/data/DataFiQuSConductor.py +97 -84
- fiqus/data/DataFiQuSConductorAC_Strand.py +701 -565
- fiqus/data/DataModelCommon.py +439 -0
- fiqus/data/DataMultipole.py +0 -13
- fiqus/data/DataRoxieParser.py +7 -0
- fiqus/data/DataWindingsCCT.py +37 -37
- fiqus/data/RegionsModelFiQuS.py +61 -104
- fiqus/geom_generators/GeometryCCT.py +904 -905
- fiqus/geom_generators/GeometryConductorAC_Strand.py +1863 -1391
- fiqus/geom_generators/GeometryMultipole.py +5 -4
- fiqus/geom_generators/GeometryPancake3D.py +1 -1
- fiqus/getdp_runners/RunGetdpCCT.py +13 -4
- fiqus/getdp_runners/RunGetdpConductorAC_Strand.py +341 -201
- fiqus/getdp_runners/RunGetdpPancake3D.py +2 -2
- fiqus/mains/MainConductorAC_Strand.py +141 -133
- fiqus/mains/MainMultipole.py +6 -5
- fiqus/mains/MainPancake3D.py +3 -4
- fiqus/mesh_generators/MeshCCT.py +209 -209
- fiqus/mesh_generators/MeshConductorAC_Strand.py +709 -656
- fiqus/mesh_generators/MeshMultipole.py +43 -46
- fiqus/parsers/ParserDAT.py +16 -16
- fiqus/parsers/ParserGetDPOnSection.py +212 -212
- fiqus/parsers/ParserGetDPTimeTable.py +134 -134
- fiqus/parsers/ParserMSH.py +53 -53
- fiqus/parsers/ParserPOS.py +214 -214
- fiqus/parsers/ParserRES.py +142 -142
- fiqus/plotters/PlotPythonCCT.py +133 -133
- fiqus/plotters/PlotPythonConductorAC.py +1079 -855
- fiqus/plotters/PlotPythonMultipole.py +18 -18
- fiqus/post_processors/PostProcessCCT.py +444 -440
- fiqus/post_processors/PostProcessConductorAC.py +997 -49
- fiqus/post_processors/PostProcessMultipole.py +19 -19
- fiqus/pre_processors/PreProcessCCT.py +175 -175
- fiqus/pro_material_functions/ironBHcurves.pro +246 -246
- fiqus/pro_templates/combined/CCT_template.pro +275 -274
- fiqus/pro_templates/combined/ConductorAC_template.pro +1474 -1025
- fiqus/pro_templates/combined/Multipole_template.pro +5 -5
- fiqus/utils/Utils.py +12 -7
- {fiqus-2025.2.0.dist-info → fiqus-2025.10.0.dist-info}/METADATA +65 -63
- fiqus-2025.10.0.dist-info/RECORD +86 -0
- {fiqus-2025.2.0.dist-info → fiqus-2025.10.0.dist-info}/WHEEL +1 -1
- tests/test_geometry_generators.py +4 -0
- tests/test_mesh_generators.py +5 -0
- tests/test_solvers.py +41 -4
- tests/utils/fiqus_test_classes.py +15 -6
- tests/utils/generate_reference_files_ConductorAC.py +57 -57
- tests/utils/helpers.py +97 -97
- fiqus-2025.2.0.dist-info/RECORD +0 -85
- {fiqus-2025.2.0.dist-info → fiqus-2025.10.0.dist-info}/LICENSE.txt +0 -0
- {fiqus-2025.2.0.dist-info → fiqus-2025.10.0.dist-info}/top_level.txt +0 -0
|
@@ -1,855 +1,1079 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import numpy as np
|
|
3
|
-
import
|
|
4
|
-
import matplotlib.pyplot as plt
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
import
|
|
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
|
-
This class
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
self.
|
|
96
|
-
self.
|
|
97
|
-
self.
|
|
98
|
-
self.
|
|
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
|
-
self.
|
|
136
|
-
self.
|
|
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
|
-
plt.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
self.
|
|
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
|
-
self.
|
|
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
|
-
if
|
|
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
|
-
if
|
|
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
|
-
if
|
|
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
|
-
|
|
1
|
+
import os, re, datetime, getpass, logging, sys
|
|
2
|
+
import numpy as np
|
|
3
|
+
import subprocess as sp
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
|
|
6
|
+
from scipy import interpolate, constants
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from matplotlib.ticker import FuncFormatter
|
|
9
|
+
from matplotlib.animation import FuncAnimation
|
|
10
|
+
from matplotlib.backends.backend_pdf import PdfPages, Stream, PdfFile
|
|
11
|
+
from fiqus.utils.Utils import FilesAndFolders
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class PlotData:
|
|
16
|
+
x_data: np.ndarray
|
|
17
|
+
y_data: np.ndarray
|
|
18
|
+
label: str
|
|
19
|
+
color: np.ndarray
|
|
20
|
+
linestyle: str = '-'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PDFreport:
|
|
24
|
+
def __init__(self, filepath, title='PostprocReport'):
|
|
25
|
+
self.creation_date = datetime.datetime.now()
|
|
26
|
+
self.author = getpass.getuser()
|
|
27
|
+
self.file = os.path.join(filepath, str(title) + '.pdf')
|
|
28
|
+
self.pdf = PdfPages(self.file)
|
|
29
|
+
# init metadata
|
|
30
|
+
self.add_metadata()
|
|
31
|
+
|
|
32
|
+
def multi_sim_report(self):
|
|
33
|
+
sims = self.plotter.simulation_collection
|
|
34
|
+
|
|
35
|
+
def add_table(self, title, data, row_header=None, col_header=None):
|
|
36
|
+
title_text = title
|
|
37
|
+
footer_text = self.author + ' - ' + str(datetime.datetime.now())
|
|
38
|
+
fig_background_color = 'white'
|
|
39
|
+
fig_border = 'steelblue'
|
|
40
|
+
|
|
41
|
+
column_headers = ['taus (s)', 'kappas (A)', 'alphas']
|
|
42
|
+
row_headers = []
|
|
43
|
+
# while I'm at it.
|
|
44
|
+
cell_text = []
|
|
45
|
+
for idx, row in enumerate(data):
|
|
46
|
+
cell_text.append([f'{x:f}' for x in row])
|
|
47
|
+
row_headers.append('k=' + str(idx + 1))
|
|
48
|
+
# Get some lists of color specs for row and column headers
|
|
49
|
+
rcolors = plt.cm.BuPu(np.full(len(row_headers), 0.1))
|
|
50
|
+
ccolors = plt.cm.BuPu(np.full(len(column_headers), 0.1)) # Create the figure. Setting a small pad on tight_layout
|
|
51
|
+
# seems to better regulate white space. Sometimes experimenting
|
|
52
|
+
# with an explicit figsize here can produce better outcome.
|
|
53
|
+
plt.figure(linewidth=2, edgecolor=fig_border, facecolor=fig_background_color, tight_layout={'pad': 1}, figsize=(5, 3))
|
|
54
|
+
the_table = plt.table(cellText=cell_text, rowLabels=row_headers, rowColours=rcolors, rowLoc='right',
|
|
55
|
+
colColours=ccolors, colLabels=column_headers, loc='center')
|
|
56
|
+
# Scaling is the only influence we have over top and bottom cell padding.
|
|
57
|
+
# Make the rows taller (i.e., make cell y scale larger).
|
|
58
|
+
the_table.scale(1, 1.5) # Hide axes
|
|
59
|
+
ax = plt.gca()
|
|
60
|
+
ax.get_xaxis().set_visible(False)
|
|
61
|
+
ax.get_yaxis().set_visible(False) # Hide axes border
|
|
62
|
+
plt.box(on=None) # Add title
|
|
63
|
+
plt.suptitle(title_text) # Add footer
|
|
64
|
+
plt.figtext(0.95, 0.05, footer_text, horizontalalignment='right', size=6, weight='light') # Force the figure to update, so backends center objects correctly within the figure.
|
|
65
|
+
# Without plt.draw() here, the title will center on the axes and not the figure.
|
|
66
|
+
plt.draw() # Create image. plt.savefig ignores figure edge and face colors, so map them.
|
|
67
|
+
fig = plt.gcf()
|
|
68
|
+
self.savefigs()
|
|
69
|
+
plt.close()
|
|
70
|
+
|
|
71
|
+
def savefigs(self):
|
|
72
|
+
""" Save all open figures one on each page """
|
|
73
|
+
for i in plt.get_fignums():
|
|
74
|
+
self.pdf.savefig(i)
|
|
75
|
+
|
|
76
|
+
def add_metadata(self):
|
|
77
|
+
d = self.pdf.infodict()
|
|
78
|
+
d['Title'] = 'Report'
|
|
79
|
+
d['Author'] = getpass.getuser()
|
|
80
|
+
d['CreationDate'] = datetime.datetime.now()
|
|
81
|
+
|
|
82
|
+
def __del__(self):
|
|
83
|
+
""" save all generated figures and close report on destruction """
|
|
84
|
+
self.savefigs()
|
|
85
|
+
self.pdf.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class PlotPythonConductorAC:
|
|
89
|
+
"""
|
|
90
|
+
This class handles various plots for single simulation CACStrand models.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, fdm, outputs_folder_path='', simulation_data=None, flux_model=None) -> None:
|
|
94
|
+
self.fdm = fdm
|
|
95
|
+
self.outputs_folder_path = os.path.join(outputs_folder_path, fdm.magnet.postproc.output_folder) # This is the path to the folder where the postprocessed data is written
|
|
96
|
+
self.simulation_data = simulation_data
|
|
97
|
+
self.flux_model = flux_model
|
|
98
|
+
# self.pdf_report = PDFreport(fdm=fdm,author=getpass.getuser(),outputs_folder_path=self.simulation_data.solution_folder) if fdm.magnet.postproc.generate_pdf else None
|
|
99
|
+
|
|
100
|
+
def colors(self, length):
|
|
101
|
+
""" unify color scheme for all plots """
|
|
102
|
+
return plt.cm.rainbow(np.linspace(0.05, 0.9, length))
|
|
103
|
+
|
|
104
|
+
def export2txt(self, data_names, data_columns, filename):
|
|
105
|
+
""" This function can be used to export one or more arrays of simulation data in one txt file (stored in solution folder)."""
|
|
106
|
+
np_data = np.array(data_columns).T
|
|
107
|
+
np.savetxt(os.path.join(self.simulation_data.solution_folder, str(filename) + '.txt'), np_data, header=" ".join(data_names), comments="")
|
|
108
|
+
|
|
109
|
+
def plot_power_loss(self):
|
|
110
|
+
""" Compares the losses produced by the ROHF model S_chain to the corresponding filament losses """
|
|
111
|
+
|
|
112
|
+
t_final = np.array(self.simulation_data.time)[-1]
|
|
113
|
+
if self.flux_model:
|
|
114
|
+
_, p_irr_rohf, p_eddy_rohf, per_cycle_rohf, _ = self.flux_model.compute(self.simulation_data.time, self.simulation_data.I_transport, sim=self.simulation_data)
|
|
115
|
+
if self.simulation_data.f and 1 / self.simulation_data.f < t_final:
|
|
116
|
+
offset_time = t_final - 1 / self.simulation_data.f
|
|
117
|
+
offset_idx = np.abs(self.simulation_data.time - offset_time).argmin()
|
|
118
|
+
lastp_time = self.simulation_data.time[offset_idx:]
|
|
119
|
+
lastp_I = self.simulation_data.I_transport[offset_idx:]
|
|
120
|
+
lastp_int_flux = self.simulation_data.flux_internal[offset_idx:]
|
|
121
|
+
lastp_filamentloss = self.simulation_data.power["FilamentLoss"][offset_idx:]
|
|
122
|
+
# selffield_power_unitarea_loop_factor = 2 * selffield_power_unitarea # missing 2x radial contribution (eddy)
|
|
123
|
+
print("#### HYSTERETIC SURFACE ####")
|
|
124
|
+
print("simple flux surface: " + str(np.trapz(lastp_I, lastp_int_flux)))
|
|
125
|
+
print("###### PER CYCLE LOSS ######")
|
|
126
|
+
print("FilamentLoss " + str(self.simulation_data.total_power_per_cycle["FilamentLoss"]))
|
|
127
|
+
if self.flux_model: print("ROHF Pirr: " + str(per_cycle_rohf))
|
|
128
|
+
|
|
129
|
+
colorgrad = self.colors(3)
|
|
130
|
+
#### DYNAMIC LOSS ####
|
|
131
|
+
plt.figure()
|
|
132
|
+
plt.title("Instantaneous power losses")
|
|
133
|
+
plt.plot(self.simulation_data.time, self.simulation_data.power["TotalLoss"], color=colorgrad[0], linestyle='--', label="Total loss")
|
|
134
|
+
plt.plot(self.simulation_data.time, self.simulation_data.power["FilamentLoss"], color=colorgrad[1], linestyle='--', label="Filament loss")
|
|
135
|
+
plt.plot(self.simulation_data.time, self.simulation_data.power["EddyLoss"], color=colorgrad[2], linestyle='--', label="Eddy loss")
|
|
136
|
+
# plt.plot(self.time, graphic_loss, color=self.c_grad[-2], label=r'graphic $P_{\text{irr}}$')
|
|
137
|
+
if self.flux_model:
|
|
138
|
+
plt.plot(self.simulation_data.time, p_irr_rohf + p_eddy_rohf, color=colorgrad[0], label=r'ROHF ($N=$' + str(self.flux_model.N) + r') $P_{\mathrm{total}}$')
|
|
139
|
+
plt.plot(self.simulation_data.time, p_irr_rohf, color=colorgrad[1], label=r'ROHF ($N=$' + str(self.flux_model.N) + r') $P_{\mathrm{irr}}$')
|
|
140
|
+
plt.plot(self.simulation_data.time, p_eddy_rohf, color=colorgrad[2], label=r'ROHF ($N=$' + str(self.flux_model.N) + r') $P_{\mathrm{eddy}}$')
|
|
141
|
+
# plt.plot(self.simulation_data.time[:len(p_irr_rohf)], self.flux_model.Ec_0 * (abs(self.simulation_data.I_transport[:len(p_irr_rohf)])/self.flux_model.Ic_0)**(self.flux_model.n) * abs(self.simulation_data.I_transport[:len(p_irr_rohf)]), color=self.c_grad[-1], label = r'power-law Filament', linestyle='--')
|
|
142
|
+
# plt.plot(self.time, abs(np.gradient(selffield_loss, self.time)) * (np.pi * strand_radius**2) , label=r'TEST selffield loss power')
|
|
143
|
+
plt.xlabel(r'Time [s]')
|
|
144
|
+
plt.ylabel(r"Power per unitlength [W']")
|
|
145
|
+
plt.legend()
|
|
146
|
+
plt.grid(linestyle='dotted')
|
|
147
|
+
|
|
148
|
+
def plot_transport(self):
|
|
149
|
+
""" This funcion makes a nice combined plot of Transport current and voltage """
|
|
150
|
+
# make fancy plot
|
|
151
|
+
|
|
152
|
+
colors = self.colors(2)
|
|
153
|
+
fig, ax1 = plt.subplots()
|
|
154
|
+
color = 'tab:red'
|
|
155
|
+
ax1.set_title("Transport current and voltage")
|
|
156
|
+
ax1.set_xlabel(r'Time $(s)$')
|
|
157
|
+
ax1.set_ylabel(r'Current $(A)$', color=colors[0])
|
|
158
|
+
ax1.plot(self.simulation_data.time, self.simulation_data.I_transport, color=colors[0])
|
|
159
|
+
ax1.grid(axis='x', linestyle='dotted')
|
|
160
|
+
ax1.tick_params(axis='y', labelcolor=colors[0])
|
|
161
|
+
|
|
162
|
+
ax2 = ax1.twinx()
|
|
163
|
+
color = 'tab:blue'
|
|
164
|
+
ax2.set_ylabel(r'Voltage $(V)$', color=colors[1])
|
|
165
|
+
ax2.plot(self.simulation_data.time, self.simulation_data.V_transport_unitlen, color=colors[1])
|
|
166
|
+
ax2.tick_params(axis='y', labelcolor=colors[1])
|
|
167
|
+
ax2.grid(axis='y', linestyle='dotted')
|
|
168
|
+
fig.tight_layout()
|
|
169
|
+
|
|
170
|
+
def plot_phaseshift_voltage(self, phaseshift):
|
|
171
|
+
# removes phaseshift from voltage
|
|
172
|
+
if not self.f:
|
|
173
|
+
raise ValueError(f'No shift plot for selected source type')
|
|
174
|
+
|
|
175
|
+
time_delay = phaseshift / (360 * self.f)
|
|
176
|
+
shift_idx = (np.abs(self.simulation_data.time - time_delay)).argmin()
|
|
177
|
+
shifted_V = self.simulation_data.V_transport[shift_idx:]
|
|
178
|
+
shifted_time = self.simulation_data.time[1 + shift_idx:] - self.simulation_data.time[shift_idx]
|
|
179
|
+
|
|
180
|
+
# make fancy plot
|
|
181
|
+
fig, ax1 = plt.subplots()
|
|
182
|
+
color = 'tab:red'
|
|
183
|
+
ax1.set_title("shifted voltage (" + str(phaseshift) + " degree)")
|
|
184
|
+
ax1.set_xlabel(r'Time $(s)$')
|
|
185
|
+
ax1.set_ylabel(r'Current $(A)$', color=color)
|
|
186
|
+
ax1.plot(self.simulation_data.time, self.simulation_data.I_transport, color=color)
|
|
187
|
+
ax1.grid(axis='x', linestyle='dotted')
|
|
188
|
+
ax1.tick_params(axis='y', labelcolor=color)
|
|
189
|
+
|
|
190
|
+
ax2 = ax1.twinx()
|
|
191
|
+
color = 'tab:blue'
|
|
192
|
+
ax2.set_ylabel(r'Voltage $(V)$', color=color)
|
|
193
|
+
ax2.plot(shifted_time, shifted_V, color=color, linestyle='dashed')
|
|
194
|
+
ax2.tick_params(axis='y', labelcolor=color)
|
|
195
|
+
ax2.grid(axis='y', linestyle='dotted')
|
|
196
|
+
fig.tight_layout()
|
|
197
|
+
|
|
198
|
+
def plot_flux(self):
|
|
199
|
+
""" This plots the internal flux obtained through two approaches and compares them with an s_chain model """
|
|
200
|
+
# L_base = (self.simulation_data.flux_internal[1]-self.simulation_data.flux_internal[0])/(self.simulation_data.I_transport[1]-self.simulation_data.I_transport[0])
|
|
201
|
+
if self.flux_model:
|
|
202
|
+
flux_rohf, _, _, _, _ = self.flux_model.compute(self.simulation_data.time, self.simulation_data.I_transport, sim=self.simulation_data)
|
|
203
|
+
|
|
204
|
+
colors = self.colors(3)
|
|
205
|
+
### SIMPLE FLUX HOMOGENIZATION ###
|
|
206
|
+
plt.figure()
|
|
207
|
+
plt.title("Internal flux ")
|
|
208
|
+
plt.xlabel(r"Transport current $[A]$")
|
|
209
|
+
plt.ylabel(r"Flux per unitlength $[Tm]$")
|
|
210
|
+
# plt.plot(self.I_transport, self.flux_external, color=self.c_grad[2], label = r'$\Phi_{\text{ext}}$')
|
|
211
|
+
plt.plot(self.simulation_data.I_transport, self.simulation_data.flux_internal, color=colors[0], label=r'$\Phi_{\text{int}}$')
|
|
212
|
+
plt.plot(self.simulation_data.I_transport, constants.mu_0 / (4 * np.pi) * self.simulation_data.I_transport, color=colors[1], label=r"$L'=\mu_0/(4 \pi)$")
|
|
213
|
+
if self.flux_model: plt.plot(self.simulation_data.I_transport, flux_rohf, color=colors[2], label=r'$\Phi_{\text{ROHF}}$ ($N=$' + str(self.flux_model.N) + ')')
|
|
214
|
+
plt.grid(linestyle='dotted')
|
|
215
|
+
plt.ticklabel_format(style='sci', axis='both', scilimits=(0, 0))
|
|
216
|
+
plt.legend()
|
|
217
|
+
plt.grid(axis='y', linestyle='dotted')
|
|
218
|
+
|
|
219
|
+
### FLUX BASED VOLTAGE ###
|
|
220
|
+
if self.flux_model: voltage_rohf = -np.gradient((flux_rohf + self.simulation_data.flux_external), self.simulation_data.time)
|
|
221
|
+
plt.figure()
|
|
222
|
+
plt.title("flux based Voltage")
|
|
223
|
+
plt.xlabel(r"Time $[s]$")
|
|
224
|
+
plt.ylabel(r"Voltage per unit-length $\left[\frac{V}{m}\right]$")
|
|
225
|
+
# plt.plot(self.time, -np.gradient((self.flux_internal + self.flux_external), self.time), color=self.c_grad[3], label = r'$-\frac{d\Phi}{dt}$ graphic')
|
|
226
|
+
if self.flux_model: plt.plot(self.simulation_data.time, voltage_rohf, color=colors[2], label=r'$-\frac{d\Phi}{dt}$ ROHF ($N=$' + str(self.flux_model.N) + ')')
|
|
227
|
+
plt.plot(self.simulation_data.time, self.simulation_data.V_transport_unitlen, color=colors[0], linestyle='--', label=r'$V$ transport')
|
|
228
|
+
plt.grid(linestyle='dotted')
|
|
229
|
+
plt.ticklabel_format(style='sci', axis='x', scilimits=(0, 0))
|
|
230
|
+
plt.legend()
|
|
231
|
+
plt.grid(axis='y', linestyle='dotted')
|
|
232
|
+
|
|
233
|
+
V = self.simulation_data.V_transport_unitlen
|
|
234
|
+
I = self.simulation_data.I_transport
|
|
235
|
+
idx = min(range(len(V)), key=lambda i: abs(V[i] + 1e-4))
|
|
236
|
+
print("ciritical current: " + str(I[idx]))
|
|
237
|
+
|
|
238
|
+
# data = np.vstack([self.simulation_data.I_transport, self.simulation_data.flux_internal, flux_rohf])
|
|
239
|
+
# self.export2txt(['current','internal_flux','rohf_flux'], data, 'flux_hysteresis')
|
|
240
|
+
|
|
241
|
+
def plot_current_rings(self):
|
|
242
|
+
"""
|
|
243
|
+
This function decomposes the total transport current into an reversible and irreversible part based on inner flux computations of the filaments.
|
|
244
|
+
It is basically the dual approach to the plot_voltage_decomp() function declared here after but shouldnt suffer from its numeric instabilities.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
t_final = np.array(self.simulation_data.time)[-1]
|
|
248
|
+
if not self.simulation_data.f or 1 / self.simulation_data.f > t_final:
|
|
249
|
+
raise ValueError(r'Cant plot current rings')
|
|
250
|
+
|
|
251
|
+
### plot current threshold rings ###
|
|
252
|
+
if self.flux_model:
|
|
253
|
+
jc_real = 3.33e10
|
|
254
|
+
circle_radii = np.sqrt(self.simulation_data.conductor.strand.number_of_filaments * (self.simulation_data.conductor.strand.filament_diameter / 2) ** 2 - np.array(self.flux_model.kappas_bar) / (jc_real * np.pi))
|
|
255
|
+
circle_surfaces = np.pi * circle_radii ** 2
|
|
256
|
+
ringwidth = np.diff(np.flip(circle_radii), prepend=0)
|
|
257
|
+
|
|
258
|
+
plt.figure()
|
|
259
|
+
plt.title("ROHF single filament - field free zones")
|
|
260
|
+
for idx, radius in enumerate(circle_radii):
|
|
261
|
+
shells = [plt.Circle((2.05 * circle_radii[0] * idx, 0), radius, color=self.c_grad[idx], hatch='//', label="> " + "{:.2f}".format(self.flux_model.kappas_bar[idx]) + " A", fill=False),
|
|
262
|
+
plt.Circle((2.05 * circle_radii[0] * idx, 0), circle_radii[0], color='black', fill=False)] # contour
|
|
263
|
+
for shell in shells: plt.gca().add_patch(shell)
|
|
264
|
+
# core = plt.Circle((0,0), circle_radii[-1], color='white')
|
|
265
|
+
# plt.gca().add_patch(core)
|
|
266
|
+
plt.legend()
|
|
267
|
+
plt.ticklabel_format(style='sci', axis='both', scilimits=(0, 0))
|
|
268
|
+
plt.xlim(-1.1 * circle_radii[0], 2.1 * len(circle_radii) * circle_radii[0])
|
|
269
|
+
plt.ylim(-1.1 * circle_radii[0], 1.1 * circle_radii[0])
|
|
270
|
+
|
|
271
|
+
def plot_fft_voltage(self):
|
|
272
|
+
""" analyze the transport voltage in the frequency domain """
|
|
273
|
+
if self.simulation_data.f:
|
|
274
|
+
T = 1 / self.simulation_data.f
|
|
275
|
+
start_idx = abs(self.simulation_data.time - (max(self.simulation_data.time) - T)).argmin() - 2
|
|
276
|
+
X = np.fft.fft(self.simulation_data.V_transport_unitlen[start_idx:])
|
|
277
|
+
N = len(X)
|
|
278
|
+
n = np.arange(N)
|
|
279
|
+
freq = n / T
|
|
280
|
+
|
|
281
|
+
fig, axs = plt.subplots(2)
|
|
282
|
+
fig.suptitle(r'Frequency domain voltage')
|
|
283
|
+
axs[0].stem(freq, np.abs(X), 'b', markerfmt=" ", basefmt="-b")
|
|
284
|
+
axs[0].set_xlabel('Freq (Hz)')
|
|
285
|
+
axs[0].set_ylabel('FFT Amplitude |X(freq)|')
|
|
286
|
+
axs[0].grid(linestyle='dotted')
|
|
287
|
+
axs[0].set_xlim([0, 10 * self.simulation_data.f])
|
|
288
|
+
|
|
289
|
+
axs[1].plot(self.simulation_data.time[:start_idx - 1], self.simulation_data.V_transport_unitlen[:start_idx - 1], '--r')
|
|
290
|
+
axs[1].plot(self.simulation_data.time[start_idx:], np.fft.ifft(X), 'r', label='transport voltage')
|
|
291
|
+
axs[1].set_xlabel('Time (s)')
|
|
292
|
+
axs[1].set_ylabel('Amplitude')
|
|
293
|
+
axs[1].grid(linestyle='dotted')
|
|
294
|
+
axs[1].set_xlim([0, max(self.simulation_data.time)])
|
|
295
|
+
|
|
296
|
+
def plot_instantaneous_power(self, show: bool = True, title: str = "Power", save_plot: bool = False, save_folder_path: str = None, save_file_name: str = None, overwrite: bool = False):
|
|
297
|
+
plt.figure()
|
|
298
|
+
plt.plot(self.simulation_data.power['Time'], self.simulation_data.power[self.simulation_data.power_columns[1:]], label=self.simulation_data.power_columns[1:])
|
|
299
|
+
plt.xlabel('Time [s]')
|
|
300
|
+
plt.ylabel('Power [W/m]')
|
|
301
|
+
plt.legend()
|
|
302
|
+
|
|
303
|
+
# Configure title:
|
|
304
|
+
# Class attributes can be accessed by using '<< ... >>' in the title string.
|
|
305
|
+
commands = re.findall('<<(.*?)>>', title)
|
|
306
|
+
for c in commands:
|
|
307
|
+
title = title.replace(f"<<{c}>>", str(eval('self.simulation_data.' + c)))
|
|
308
|
+
plt.title(title)
|
|
309
|
+
|
|
310
|
+
if save_plot: # Save the plot
|
|
311
|
+
filePath = os.path.join(save_folder_path, str(save_file_name) + '.png')
|
|
312
|
+
plt.savefig(filePath)
|
|
313
|
+
|
|
314
|
+
if show:
|
|
315
|
+
plt.show()
|
|
316
|
+
else:
|
|
317
|
+
plt.close()
|
|
318
|
+
|
|
319
|
+
def plot_impedance(self, frequency_range):
|
|
320
|
+
""" This function compares the estimated data impedance with an analytic solution for inductive tranmission """
|
|
321
|
+
|
|
322
|
+
if not self.simulation_data.f or not self.simulation_data.analytic_RL:
|
|
323
|
+
raise ValueError(f'No impedance estimate for selected source type')
|
|
324
|
+
analytic_impedance = self.simulation_data.analytic_RL[0] + 1j * 2 * np.pi * frequency_range * self.simulation_data.analytic_RL[1] # R + jwL
|
|
325
|
+
|
|
326
|
+
fig, axs = plt.subplots(2)
|
|
327
|
+
fig.suptitle(r'Simulation Impedance $Z$')
|
|
328
|
+
axs[0].plot(frequency_range, np.abs(np.imag(analytic_impedance)), '-b', label="pure Inductance")
|
|
329
|
+
axs[0].plot(self.simulation_data.f, np.abs(self.simulation_data.data_impedance), 'r', marker="x", label="simulation")
|
|
330
|
+
axs[0].set_xscale('log')
|
|
331
|
+
axs[0].set_yscale('log')
|
|
332
|
+
axs[0].set_ylabel(r'Magnitude $|Z|$')
|
|
333
|
+
axs[0].grid(linestyle='dotted')
|
|
334
|
+
axs[0].legend()
|
|
335
|
+
|
|
336
|
+
axs[1].plot(frequency_range, 90 * np.ones((len(frequency_range), 1)), '-b', label="pure Inductance")
|
|
337
|
+
axs[1].plot(self.simulation_data.f, np.angle(self.simulation_data.data_impedance, deg=True), 'r', marker="x", label="simulation")
|
|
338
|
+
axs[1].set_xscale('log')
|
|
339
|
+
axs[1].set_ylim([-15, 105])
|
|
340
|
+
axs[1].set_ylabel(r'Phase $\measuredangle{Z}$' + r' $(deg)$')
|
|
341
|
+
axs[1].set_xlabel(r'Frequency $(Hz)$')
|
|
342
|
+
axs[1].grid(linestyle='dotted')
|
|
343
|
+
axs[1].legend()
|
|
344
|
+
|
|
345
|
+
def plot_resistivity(self):
|
|
346
|
+
|
|
347
|
+
time, I, flux = self.simulation_data.first_rise(current=True)
|
|
348
|
+
|
|
349
|
+
if self.flux_model:
|
|
350
|
+
flux_rohf, p_irr, _, _, _ = self.flux_model.compute(time, I, sim=self.simulation_data)
|
|
351
|
+
# p_ohm_fil, p_ohm_mat, R_fil, R_mat = self.flux_model.ohmic_loss(I, self.simulation_data)
|
|
352
|
+
|
|
353
|
+
colors = self.colors(3)
|
|
354
|
+
### SIMPLE FLUX HOMOGENIZATION ###
|
|
355
|
+
plt.figure(figsize=(5, 8))
|
|
356
|
+
plt.title("Differential resitivity")
|
|
357
|
+
plt.xlabel(r"Transport current $[A]$")
|
|
358
|
+
plt.ylabel(r"Transport voltage per unit length $[V/m]$")
|
|
359
|
+
# plt.plot(self.I_transport, self.flux_external, color=self.c_grad[2], label = r'$\Phi_{\text{ext}}$')
|
|
360
|
+
plt.plot(I, abs(self.simulation_data.V_transport_unitlen[:len(I)]), color=colors[0], label=r'FEM reference')
|
|
361
|
+
# plt.plot(I, (1.68e-10/(0.5*np.pi*(0.5e-3)**2))*I, color=self.c_grad[1], label='RRR100 copper')
|
|
362
|
+
if self.flux_model:
|
|
363
|
+
plt.plot(I, self.flux_model.Ec_0 * (abs(I) / self.flux_model.Ic_0) ** (self.flux_model.n), color=colors[1], label=r'Power-law only')
|
|
364
|
+
plt.plot(I, (p_irr) / I, color=colors[2], label=r'ROHF current sharing')
|
|
365
|
+
plt.axhline(y=self.flux_model.Ec_0, color='black', linestyle='--', label=r'$E_c = 1^{-4} \mathrm{V}/\mathrm{m}$')
|
|
366
|
+
# plt.axis([0.7*self.flux_model.Ic_0, 1.2*self.flux_model.Ic_0, 0, 100*self.flux_model.Ec_0])
|
|
367
|
+
plt.grid(linestyle='dotted')
|
|
368
|
+
plt.ticklabel_format(style='sci', axis='both', scilimits=(0, 0))
|
|
369
|
+
plt.legend()
|
|
370
|
+
plt.grid(axis='y', linestyle='dotted')
|
|
371
|
+
|
|
372
|
+
def show(self):
|
|
373
|
+
plt.show()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class BatchPlotPythonConductorAC:
|
|
377
|
+
"""
|
|
378
|
+
This class loads and stores the data from the simulations specified in a csv file and can apply various postprocessing operations on the data.
|
|
379
|
+
The data from each simulation is saved as a SimulationData object which is subsequently stored in a list in this class.
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
def __init__(self, fdm, lossMap_gridData_folder=None, outputs_folder_path='', simulation_collection=None, fluxmodel_collection=None) -> None:
|
|
383
|
+
self.fdm = fdm
|
|
384
|
+
self.outputs_folder_path = os.path.join(outputs_folder_path, fdm.magnet.postproc.output_folder) # This is the path to the folder where the postprocessed data is written
|
|
385
|
+
self.simulation_collection = simulation_collection
|
|
386
|
+
self.fluxmodel_collection = fluxmodel_collection
|
|
387
|
+
try:
|
|
388
|
+
sp.run(['pdflatex', '--version'], stdout=sp.DEVNULL, stderr=sp.DEVNULL)
|
|
389
|
+
tex_installation = True
|
|
390
|
+
except:
|
|
391
|
+
tex_installation = False
|
|
392
|
+
self.tex_installation = tex_installation
|
|
393
|
+
|
|
394
|
+
if not os.path.exists(self.outputs_folder_path):
|
|
395
|
+
os.makedirs(self.outputs_folder_path)
|
|
396
|
+
|
|
397
|
+
if lossMap_gridData_folder is not None:
|
|
398
|
+
self.totalLoss_gridData = self.load_lossMap_gridData('TotalLoss', lossMap_gridData_folder)
|
|
399
|
+
self.filamentLoss_gridData = self.load_lossMap_gridData('FilamentLoss', lossMap_gridData_folder)
|
|
400
|
+
self.eddyLoss_gridData = self.load_lossMap_gridData('EddyLoss', lossMap_gridData_folder)
|
|
401
|
+
self.couplingLoss_gridData = self.load_lossMap_gridData('CouplingLoss', lossMap_gridData_folder)
|
|
402
|
+
|
|
403
|
+
def colors(self, length):
|
|
404
|
+
""" unify color scheme for plots """
|
|
405
|
+
return plt.cm.rainbow(np.linspace(0.05, 0.9, length))
|
|
406
|
+
|
|
407
|
+
def create_non_overwriting_filepath(self, folder_path, base_name, extension, overwrite):
|
|
408
|
+
"""
|
|
409
|
+
Creates a filepath that does not overwrite any existing files.
|
|
410
|
+
|
|
411
|
+
This function checks if a file already exists at the specified filepath. If the file exists and `overwrite` is False,
|
|
412
|
+
it modifies the filepath to create a new file instead of overwriting the existing one.
|
|
413
|
+
If `overwrite` is True or the file does not exist, it returns the filepath as it is.
|
|
414
|
+
|
|
415
|
+
Parameters
|
|
416
|
+
----------
|
|
417
|
+
folder_path : str
|
|
418
|
+
The path to the folder where the file will be created.
|
|
419
|
+
base_name : str
|
|
420
|
+
The base name of the file.
|
|
421
|
+
extension : str
|
|
422
|
+
The extension of the file.
|
|
423
|
+
overwrite : bool, optional
|
|
424
|
+
If True, the function will overwrite an existing file. If False, the function will modify the filepath to avoid overwriting. Defaults to False.
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
str
|
|
429
|
+
The final filepath. If `overwrite` is False and a file already exists at the original filepath, this will be a new filepath that does not overwrite any existing files.
|
|
430
|
+
"""
|
|
431
|
+
if os.path.exists(os.path.join(folder_path, base_name + extension)) and not overwrite:
|
|
432
|
+
counter = 1
|
|
433
|
+
new_name = base_name + f"_{counter}" + extension
|
|
434
|
+
while os.path.exists(os.path.join(folder_path, new_name)):
|
|
435
|
+
new_name = base_name + f"_{counter}" + extension
|
|
436
|
+
counter += 1
|
|
437
|
+
return os.path.join(folder_path, new_name)
|
|
438
|
+
|
|
439
|
+
return os.path.join(folder_path, base_name + extension)
|
|
440
|
+
|
|
441
|
+
def lossMap_createGridData(self, lossType='TotalLoss', x_val_to_include=None, y_val_to_include=None):
|
|
442
|
+
"""
|
|
443
|
+
This function creates the grid data needed for the loss map, based on the yaml input file.
|
|
444
|
+
Given a collection of simulations it interpolates the loss data between the datapoints to a grid and returns the grid data.
|
|
445
|
+
"""
|
|
446
|
+
lm = self.fdm.magnet.postproc.batch_postproc.loss_map
|
|
447
|
+
|
|
448
|
+
# Extract data from simulation collection and normalize
|
|
449
|
+
x_arr = np.array([eval('sd.' + lm.x_val) / lm.x_norm for sd in self.simulation_collection])
|
|
450
|
+
y_arr = np.array([eval('sd.' + lm.y_val) / lm.y_norm for sd in self.simulation_collection])
|
|
451
|
+
loss = np.array([sd.total_power_per_cycle[lossType] / lm.loss_norm for sd in self.simulation_collection])
|
|
452
|
+
|
|
453
|
+
# Logarithmic scaling
|
|
454
|
+
if lm.x_log: x_arr = np.log10(x_arr)
|
|
455
|
+
if lm.y_log: y_arr = np.log10(y_arr)
|
|
456
|
+
if lm.loss_log: loss = np.log10(loss)
|
|
457
|
+
|
|
458
|
+
x_arr_interpolated = np.linspace(min(x_arr), max(x_arr), lm.x_steps)
|
|
459
|
+
y_arr_interpolated = np.linspace(min(y_arr), max(y_arr), lm.y_steps)
|
|
460
|
+
# Insert specific values to the grid if they are not already included (useful for cross sections)
|
|
461
|
+
if x_val_to_include is not None and x_val_to_include not in x_arr_interpolated:
|
|
462
|
+
x_arr_interpolated = np.insert(x_arr_interpolated, np.where(x_arr_interpolated > x_val_to_include)[0][0], x_val_to_include)
|
|
463
|
+
if y_val_to_include is not None and y_val_to_include not in y_arr_interpolated:
|
|
464
|
+
y_arr_interpolated = np.insert(y_arr_interpolated, np.where(y_arr_interpolated > y_val_to_include)[0][0], y_val_to_include)
|
|
465
|
+
|
|
466
|
+
# Create grid
|
|
467
|
+
X, Y = np.meshgrid(x_arr_interpolated, y_arr_interpolated, indexing='ij')
|
|
468
|
+
gridPoints = np.c_[X.ravel(), Y.ravel()]
|
|
469
|
+
dataPoints = np.c_[x_arr, y_arr]
|
|
470
|
+
|
|
471
|
+
# Interpolate the simulation data onto the grid
|
|
472
|
+
V = interpolate.griddata(
|
|
473
|
+
dataPoints,
|
|
474
|
+
loss,
|
|
475
|
+
gridPoints,
|
|
476
|
+
method='linear' # Cubic produces cleaner plots. Any incentive to go back to linear?
|
|
477
|
+
).reshape(X.shape)
|
|
478
|
+
|
|
479
|
+
return X, Y, V, dataPoints
|
|
480
|
+
|
|
481
|
+
def save_lossMap_gridData(self, save_folder_name='lossMap_gridData'):
|
|
482
|
+
"""
|
|
483
|
+
This function calls the lossMap_createGridData function and saves the grid data.
|
|
484
|
+
"""
|
|
485
|
+
lm = self.fdm.magnet.postproc.batch_postproc.loss_map
|
|
486
|
+
|
|
487
|
+
lossTypes = ['TotalLoss', 'FilamentLoss', 'EddyLoss', 'CouplingLoss', 'CouplingLoss_dyn', 'TotalLoss_dyn'] # Only in the case dynamic correction is used, must be changed later
|
|
488
|
+
# 1) Create a folder to store the output files
|
|
489
|
+
gridData_folder_path = self.create_non_overwriting_filepath(self.outputs_folder_path, save_folder_name, '', self.fdm.run.overwrite)
|
|
490
|
+
if not os.path.exists(gridData_folder_path): os.makedirs(gridData_folder_path)
|
|
491
|
+
# 2) Create the grid data for each loss type and save it
|
|
492
|
+
for lossType in lossTypes:
|
|
493
|
+
X, Y, V, _ = self.lossMap_createGridData(lossType)
|
|
494
|
+
if lm.x_log: X = np.power(10, X)
|
|
495
|
+
if lm.y_log: Y = np.power(10, Y)
|
|
496
|
+
if lm.loss_log: V = np.power(10, V)
|
|
497
|
+
np.savetxt(os.path.join(gridData_folder_path, f'{lossType}.txt'), np.column_stack((X.ravel(), Y.ravel(), V.ravel())), delimiter=' ', header=f'{lm.x_val} {lm.y_val} {lossType}', comments='')
|
|
498
|
+
|
|
499
|
+
def load_lossMap_gridData(self, lossType='TotalLoss', save_folder_name='lossMap_gridData'):
|
|
500
|
+
"""
|
|
501
|
+
This function loads the grid data for a given loss type.
|
|
502
|
+
"""
|
|
503
|
+
lm = self.fdm.magnet.postproc.batch_postproc.loss_map
|
|
504
|
+
gridData_folder_path = os.path.join(self.inputs_folder_path, save_folder_name)
|
|
505
|
+
|
|
506
|
+
if not os.path.exists(gridData_folder_path):
|
|
507
|
+
raise FileNotFoundError(f'The folder {gridData_folder_path} does not exist.')
|
|
508
|
+
|
|
509
|
+
X, Y, V = np.loadtxt(os.path.join(gridData_folder_path, f'{lossType}.txt'), unpack=True, skiprows=1)
|
|
510
|
+
|
|
511
|
+
if lm.x_log: X = np.log10(X)
|
|
512
|
+
if lm.y_log: Y = np.log10(Y)
|
|
513
|
+
if lm.loss_log: V = np.log10(V)
|
|
514
|
+
|
|
515
|
+
# Get the unique counts of X and Y
|
|
516
|
+
unique_X = np.unique(X)
|
|
517
|
+
unique_Y = np.unique(Y)
|
|
518
|
+
|
|
519
|
+
# Reshape the data
|
|
520
|
+
X = X.reshape((len(unique_X), len(unique_Y)))
|
|
521
|
+
Y = Y.reshape((len(unique_X), len(unique_Y)))
|
|
522
|
+
V = V.reshape((len(unique_X), len(unique_Y)))
|
|
523
|
+
|
|
524
|
+
return X, Y, V
|
|
525
|
+
|
|
526
|
+
def save_magnetization(self):
|
|
527
|
+
"""
|
|
528
|
+
This function saves the magnetization data for all simulations in the simulation collection.
|
|
529
|
+
"""
|
|
530
|
+
magnetization_folder_path = self.create_non_overwriting_filepath(self.outputs_folder_path, 'magnetization', '', self.fdm.run.overwrite)
|
|
531
|
+
if not os.path.exists(magnetization_folder_path): os.makedirs(magnetization_folder_path)
|
|
532
|
+
for sd in self.simulation_collection:
|
|
533
|
+
magnetization = sd.magn_fil + sd.magn_matrix
|
|
534
|
+
magnetization = np.c_[sd.time, magnetization]
|
|
535
|
+
np.savetxt(os.path.join(magnetization_folder_path, f'magn_f{sd.solve.source_parameters.sine.frequency}_b{sd.solve.source_parameters.sine.field_amplitude}_I{sd.solve.source_parameters.sine.current_amplitude}.txt'), magnetization, delimiter=' ', header='t x y z', comments='')
|
|
536
|
+
|
|
537
|
+
def lossMap_crossSection(self, slice_value, axis_to_cut='x'):
|
|
538
|
+
"""
|
|
539
|
+
This function returns the data corresponding to a cross section of the loss map, for all loss types.
|
|
540
|
+
Given an axis and a value, it sweeps the other axis for the closest value and returns the data.
|
|
541
|
+
Example: Given slice value 0 and axis x, it returns the data for the cross section at x = 0.
|
|
542
|
+
"""
|
|
543
|
+
|
|
544
|
+
lm = self.fdm.magnet.postproc.batch_postproc.loss_map
|
|
545
|
+
if axis_to_cut == 'x':
|
|
546
|
+
x_val_to_include = slice_value
|
|
547
|
+
y_val_to_include = None
|
|
548
|
+
elif axis_to_cut == 'y':
|
|
549
|
+
x_val_to_include = None
|
|
550
|
+
y_val_to_include = slice_value
|
|
551
|
+
X, Y, V, dataPoints = self.lossMap_createGridData('TotalLoss', x_val_to_include, y_val_to_include)
|
|
552
|
+
_, _, FilamentLoss, _ = self.lossMap_createGridData('FilamentLoss', x_val_to_include, y_val_to_include)
|
|
553
|
+
_, _, EddyLoss, _ = self.lossMap_createGridData('EddyLoss', x_val_to_include, y_val_to_include)
|
|
554
|
+
_, _, CouplingLoss, _ = self.lossMap_createGridData('CouplingLoss', x_val_to_include, y_val_to_include)
|
|
555
|
+
|
|
556
|
+
if axis_to_cut == 'x':
|
|
557
|
+
index = np.abs(X[:, 0] - slice_value).argmin()
|
|
558
|
+
slice_vals = Y[index, :]
|
|
559
|
+
|
|
560
|
+
elif axis_to_cut == 'y':
|
|
561
|
+
index = np.abs(Y[0, :] - slice_value).argmin()
|
|
562
|
+
slice_vals = X[:, index]
|
|
563
|
+
|
|
564
|
+
# Extract the loss values for the constant frequency across all applied fields
|
|
565
|
+
totalLoss = V[index, :] if axis_to_cut == 'x' else V[:, index]
|
|
566
|
+
filamentLoss = FilamentLoss[index, :] if axis_to_cut == 'x' else FilamentLoss[:, index]
|
|
567
|
+
eddyLoss = EddyLoss[index, :] if axis_to_cut == 'x' else EddyLoss[:, index]
|
|
568
|
+
couplingLoss = CouplingLoss[index, :] if axis_to_cut == 'x' else CouplingLoss[:, index]
|
|
569
|
+
|
|
570
|
+
return slice_vals, totalLoss, filamentLoss, eddyLoss, couplingLoss
|
|
571
|
+
|
|
572
|
+
def plot_lossMap_crossSection(self):
|
|
573
|
+
"""
|
|
574
|
+
This function calls the lossMap_crossSection function and plots the data it returns, which is the loss for all values of one axis, given a constant value of the other axis.
|
|
575
|
+
"""
|
|
576
|
+
|
|
577
|
+
if self.tex_installation:
|
|
578
|
+
plt.rcParams['text.usetex'] = True
|
|
579
|
+
plt.rcParams['font.family'] = 'times'
|
|
580
|
+
plt.rcParams['font.size'] = 20
|
|
581
|
+
|
|
582
|
+
lm = self.fdm.magnet.postproc.batch_postproc.loss_map
|
|
583
|
+
slice_value = lm.cross_section.cut_value
|
|
584
|
+
axis_to_cut = lm.cross_section.axis_to_cut
|
|
585
|
+
|
|
586
|
+
if (lm.x_log and axis_to_cut == 'x') or (lm.y_log and axis_to_cut == 'y'):
|
|
587
|
+
slice_value = np.log10(slice_value)
|
|
588
|
+
|
|
589
|
+
slice_vals, totalLoss, filamentLoss, eddyLoss, couplingLoss = self.lossMap_crossSection(slice_value, axis_to_cut=axis_to_cut)
|
|
590
|
+
|
|
591
|
+
def log_formatter(x, pos):
|
|
592
|
+
"""
|
|
593
|
+
Format the tick labels on the plot.
|
|
594
|
+
"""
|
|
595
|
+
return f"$10^{{{int(x)}}}$"
|
|
596
|
+
|
|
597
|
+
# Plot the loss with respect to applied field for the constant frequency
|
|
598
|
+
fig, ax = plt.subplots(figsize=(8, 6))
|
|
599
|
+
ax.plot(slice_vals, totalLoss, label=f'Total Loss')
|
|
600
|
+
ax.plot(slice_vals, filamentLoss, label=f'Filament Loss')
|
|
601
|
+
ax.plot(slice_vals, eddyLoss, label=f'Eddy Loss')
|
|
602
|
+
ax.plot(slice_vals, couplingLoss, label=f'Coupling Loss')
|
|
603
|
+
|
|
604
|
+
tick_formatter = FuncFormatter(log_formatter)
|
|
605
|
+
if lm.x_log and axis_to_cut == 'y' or lm.y_log and axis_to_cut == 'x':
|
|
606
|
+
ax.xaxis.set_major_formatter(tick_formatter)
|
|
607
|
+
if lm.loss_log:
|
|
608
|
+
ax.yaxis.set_major_formatter(tick_formatter)
|
|
609
|
+
|
|
610
|
+
title = lm.cross_section.title.replace('<<cut_value>>', str(round(10 ** slice_value, 3)))
|
|
611
|
+
ax.set_title(title)
|
|
612
|
+
ax.set_xlabel(lm.ylabel if axis_to_cut == 'x' else lm.xlabel)
|
|
613
|
+
ax.set_ylabel(lm.cross_section.ylabel)
|
|
614
|
+
ax.legend()
|
|
615
|
+
|
|
616
|
+
# np.savetxt(os.path.join(self.outputs_folder_path, 'lossMaps_cut_0p2T_0A.txt'), np.column_stack((10**slice_vals, 10**totalLoss, 10**eddyLoss, 10**couplingLoss, 10**filamentLoss)), delimiter=' ', header='f total eddy coupling filament', comments='')
|
|
617
|
+
|
|
618
|
+
if lm.cross_section.save_plot:
|
|
619
|
+
filePath = self.create_non_overwriting_filepath(self.outputs_folder_path, lm.cross_section.filename, '.png', self.fdm.run.overwrite)
|
|
620
|
+
plt.savefig(filePath)
|
|
621
|
+
|
|
622
|
+
if self.fdm.run.launch_gui: plt.show()
|
|
623
|
+
|
|
624
|
+
def plot_percycle_losses(self, sims, flux_model, title):
|
|
625
|
+
""" This function produces a frequency to per cycle power loss plot used within the ROHF parameter optimization """
|
|
626
|
+
|
|
627
|
+
pc_irr_rohf = []
|
|
628
|
+
pc_eddy_rohf = []
|
|
629
|
+
pc_fil_sim = [sim.total_power_per_cycle['FilamentLoss'] for sim in sims]
|
|
630
|
+
pc_eddy_sim = [sim.total_power_per_cycle['EddyLoss'] for sim in sims]
|
|
631
|
+
freq = [sim.f for sim in sims]
|
|
632
|
+
|
|
633
|
+
for sim in sims:
|
|
634
|
+
_, _, _, irr, eddy = flux_model.compute(time=sim.time, I=sim.I_transport, sim=sim)
|
|
635
|
+
pc_irr_rohf.append(irr)
|
|
636
|
+
pc_eddy_rohf.append(eddy)
|
|
637
|
+
|
|
638
|
+
plt.figure()
|
|
639
|
+
plt.title(title)
|
|
640
|
+
plt.plot(freq, pc_fil_sim, 'o-', label='FilamentLoss')
|
|
641
|
+
plt.plot(freq, pc_irr_rohf, 'x-', label=r'$P_{\mathrm{irr}}$ ROHF')
|
|
642
|
+
plt.plot(freq, pc_eddy_sim, 'o-', label='EddyLoss')
|
|
643
|
+
plt.plot(freq, pc_eddy_rohf, 'x-', label=r'$P_{\mathrm{eddy}}$ ROHF')
|
|
644
|
+
plt.legend()
|
|
645
|
+
plt.xlabel(r'Frequency ($\mathrm{Hz}$)')
|
|
646
|
+
plt.ylabel(r'Per cycle power loss ($\mathrm{W/m}$)')
|
|
647
|
+
plt.xscale('log')
|
|
648
|
+
plt.yscale('log')
|
|
649
|
+
|
|
650
|
+
def plot_flux_time_triplet(self, flux_model, title):
|
|
651
|
+
|
|
652
|
+
amplitudes = sorted(list(set([sim.I_amp for sim in self.simulation_collection])))
|
|
653
|
+
|
|
654
|
+
high_I_sims = []
|
|
655
|
+
mid_I_sims = []
|
|
656
|
+
low_I_sims = []
|
|
657
|
+
for sim in self.simulation_collection:
|
|
658
|
+
if sim.I_amp == amplitudes[-1]:
|
|
659
|
+
high_I_sims.append(sim)
|
|
660
|
+
elif sim.I_amp == amplitudes[-2]:
|
|
661
|
+
mid_I_sims.append(sim)
|
|
662
|
+
elif sim.I_amp == amplitudes[2]:
|
|
663
|
+
low_I_sims.append(sim)
|
|
664
|
+
|
|
665
|
+
colors = self.colors(max([len(high_I_sims), len(mid_I_sims), len(low_I_sims)]))
|
|
666
|
+
plt.figure()
|
|
667
|
+
plt.title(title)
|
|
668
|
+
plt.subplot(3, 1, 1)
|
|
669
|
+
for i, sim in enumerate(high_I_sims):
|
|
670
|
+
plt.plot(sim.time * sim.f, sim.flux_internal, color=colors[i], linestyle='--')
|
|
671
|
+
plt.plot(sim.time * sim.f, flux_model.compute(I=sim.I_transport, time=sim.time, sim=sim)[0], color=colors[i], label=str(sim.f) + ' Hz')
|
|
672
|
+
plt.legend()
|
|
673
|
+
|
|
674
|
+
plt.subplot(3, 1, 2)
|
|
675
|
+
for i, sim in enumerate(mid_I_sims):
|
|
676
|
+
plt.plot(sim.time * sim.f, sim.flux_internal, color=colors[i], linestyle='--')
|
|
677
|
+
plt.plot(sim.time * sim.f, flux_model.compute(I=sim.I_transport, time=sim.time, sim=sim)[0], color=colors[i], label=str(sim.f) + ' Hz')
|
|
678
|
+
|
|
679
|
+
plt.subplot(3, 1, 3)
|
|
680
|
+
for i, sim in enumerate(low_I_sims):
|
|
681
|
+
plt.plot(sim.time * sim.f, sim.flux_internal, color=colors[i], linestyle='--')
|
|
682
|
+
plt.plot(sim.time * sim.f, flux_model.compute(I=sim.I_transport, time=sim.time, sim=sim)[0], color=colors[i], label=str(sim.f) + ' Hz')
|
|
683
|
+
# plt.ylabel(r'Internal Flux per unit length ($\mathrm{Tm}$)')
|
|
684
|
+
plt.text(0.04, 0.5, r'Internal Flux per unit length ($\mathrm{Tm}$)', va='center', rotation='vertical')
|
|
685
|
+
plt.xlabel(r'Time in periods')
|
|
686
|
+
|
|
687
|
+
def animate_lossMap_crossSection(self):
|
|
688
|
+
"""
|
|
689
|
+
This function is similar to the plot_lossMap_crossSection function, but instead of plotting the loss for at a constant crossection,
|
|
690
|
+
it sweeps the crossection over a chosen axis and plots the loss for each crossection as an animation.
|
|
691
|
+
"""
|
|
692
|
+
lm = self.fdm.magnet.postproc.batch_postproc.loss_map
|
|
693
|
+
axis = lm.cross_section_sweep.axis_to_sweep
|
|
694
|
+
|
|
695
|
+
X, Y, V, dataPoints = self.lossMap_createGridData('TotalLoss')
|
|
696
|
+
x_vals = X[:, 0] # x-values from the loss map
|
|
697
|
+
y_vals = Y[0, :] # y-values from the loss map
|
|
698
|
+
|
|
699
|
+
if axis == 'x':
|
|
700
|
+
A = np.zeros((lm.y_steps, 4, lm.x_steps))
|
|
701
|
+
axis_to_sweep = x_vals
|
|
702
|
+
constant_axis = y_vals
|
|
703
|
+
elif axis == 'y':
|
|
704
|
+
A = np.zeros((lm.x_steps, 4, lm.y_steps))
|
|
705
|
+
axis_to_sweep = y_vals
|
|
706
|
+
constant_axis = x_vals
|
|
707
|
+
|
|
708
|
+
for i, val in enumerate(axis_to_sweep):
|
|
709
|
+
_, totalLoss, filamentLoss, eddyLoss, couplingLoss = self.lossMap_crossSection(val, axis_to_cut=axis)
|
|
710
|
+
A[:, 0, i] = totalLoss
|
|
711
|
+
A[:, 1, i] = filamentLoss
|
|
712
|
+
A[:, 2, i] = eddyLoss
|
|
713
|
+
A[:, 3, i] = couplingLoss
|
|
714
|
+
|
|
715
|
+
# Initialize the plot
|
|
716
|
+
fig, ax = plt.subplots()
|
|
717
|
+
lines = ax.plot(constant_axis, A[:, :, 0], lw=2, label=['total Loss', 'filament Loss', 'eddy Loss', 'coupling Loss'])
|
|
718
|
+
|
|
719
|
+
# Set plot limits and labels
|
|
720
|
+
ax.set_xlim(constant_axis[0], constant_axis[-1])
|
|
721
|
+
ax.set_ylim(np.min(A), np.max(A))
|
|
722
|
+
ax.set_xlabel(lm.ylabel if axis == 'x' else lm.xlabel)
|
|
723
|
+
ax.set_ylabel(lm.cross_section_sweep.ylabel)
|
|
724
|
+
|
|
725
|
+
# Define the animation update function
|
|
726
|
+
def update(frame):
|
|
727
|
+
for i, line in enumerate(lines):
|
|
728
|
+
line.set_ydata(A[:, i, frame])
|
|
729
|
+
|
|
730
|
+
if axis == 'x':
|
|
731
|
+
if lm.x_log:
|
|
732
|
+
sweep_value = 10 ** x_vals[frame]
|
|
733
|
+
else:
|
|
734
|
+
sweep_value = x_vals[frame]
|
|
735
|
+
elif axis == 'y':
|
|
736
|
+
if lm.y_log:
|
|
737
|
+
sweep_value = 10 ** y_vals[frame]
|
|
738
|
+
else:
|
|
739
|
+
sweep_value = y_vals[frame]
|
|
740
|
+
|
|
741
|
+
title = lm.cross_section_sweep.title.replace('<<sweep_value>>', str(round(sweep_value, 3)))
|
|
742
|
+
ax.set_title(title)
|
|
743
|
+
return lines, # line1, line2, line3, line4
|
|
744
|
+
|
|
745
|
+
# Create the animation
|
|
746
|
+
dt = 0.1
|
|
747
|
+
ani = FuncAnimation(fig, update, frames=lm.x_steps if axis == 'x' else lm.y_steps, interval=dt * 1000, blit=False)
|
|
748
|
+
|
|
749
|
+
# Show the animation
|
|
750
|
+
plt.legend()
|
|
751
|
+
plt.grid()
|
|
752
|
+
|
|
753
|
+
if lm.cross_section_sweep.save_plot:
|
|
754
|
+
filepath = self.create_non_overwriting_filepath(folder_path=self.outputs_folder_path, base_name=lm.cross_section_sweep.filename, extension='.gif', overwrite=self.fdm.run.overwrite)
|
|
755
|
+
ani.save(filepath, writer='imagemagick', fps=1 / dt)
|
|
756
|
+
|
|
757
|
+
if self.fdm.run.launch_gui: plt.show()
|
|
758
|
+
|
|
759
|
+
def create_lossMap(self):
|
|
760
|
+
"""
|
|
761
|
+
This function creates a loss map based on the inputs given in the loss_map section of the input file.
|
|
762
|
+
The loss-map can be plotted and saved as a .png file.
|
|
763
|
+
"""
|
|
764
|
+
lm = self.fdm.magnet.postproc.batch_postproc.loss_map
|
|
765
|
+
|
|
766
|
+
if self.simulation_collection:
|
|
767
|
+
X, Y, V, dataPoints = self.lossMap_createGridData(lm.loss_type)
|
|
768
|
+
else:
|
|
769
|
+
X, Y, V = self.totalLoss_gridData
|
|
770
|
+
|
|
771
|
+
if self.tex_installation:
|
|
772
|
+
plt.rcParams['text.usetex'] = True
|
|
773
|
+
plt.rcParams['font.family'] = 'times'
|
|
774
|
+
plt.rcParams['font.size'] = 20
|
|
775
|
+
|
|
776
|
+
fig, ax = plt.subplots(figsize=(10, 8))
|
|
777
|
+
|
|
778
|
+
c = plt.pcolormesh(X, Y, V, shading='gouraud', cmap='plasma_r')
|
|
779
|
+
c_min = min([np.ceil(np.min(V)) for V in [V]])
|
|
780
|
+
c_max = max([np.floor(np.max(V)) for V in [V]])
|
|
781
|
+
c_ticks = [int(val) for val in np.arange(c_min, c_max + 1)]
|
|
782
|
+
cont = plt.contour(X, Y, V, c_ticks, colors='k', linestyles='dashed')
|
|
783
|
+
|
|
784
|
+
if lm.show_datapoints:
|
|
785
|
+
plt.scatter(dataPoints[:, 0], dataPoints[:, 1], s=50, edgecolors='k')
|
|
786
|
+
|
|
787
|
+
if lm.show_loss_type_dominance_contour:
|
|
788
|
+
sigmoid = lambda x: 1 / (1 + np.exp(-x))
|
|
789
|
+
if self.simulation_collection:
|
|
790
|
+
_, _, FilamentLoss, _ = self.lossMap_createGridData(lossType='FilamentLoss')
|
|
791
|
+
_, _, CouplingLoss, _ = self.lossMap_createGridData(lossType='CouplingLoss')
|
|
792
|
+
_, _, EddyLoss, _ = self.lossMap_createGridData(lossType='EddyLoss')
|
|
793
|
+
# else:
|
|
794
|
+
# _, _, FilamentLoss = self.filamentLoss_gridData
|
|
795
|
+
# _, _, CouplingLoss = self.couplingLoss_gridData
|
|
796
|
+
# _, _, EddyLoss = self.eddyLoss_gridData
|
|
797
|
+
fil_vs_coupling_loss = np.maximum(FilamentLoss, EddyLoss) - CouplingLoss
|
|
798
|
+
fil_vs_eddy_loss = EddyLoss - np.maximum(FilamentLoss, CouplingLoss)
|
|
799
|
+
plt.contour(X, Y, sigmoid(fil_vs_coupling_loss), [0.5], colors='k')
|
|
800
|
+
plt.contour(X, Y, sigmoid(fil_vs_eddy_loss), [0.5], colors='k')
|
|
801
|
+
|
|
802
|
+
cbar = fig.colorbar(c, ticks=c_ticks) # , labels=c_labels)
|
|
803
|
+
# cbar.ax.set_xticks([-7, -6, -5, -4, -3, -2, -1, 0, 1])
|
|
804
|
+
# cbar.ax.set_yticklabels([r'$10^{-7}$', r'$10^{-6}$', r'$10^{-5}$', r'$10^{-4}$', r'$10^{-3}$', r'$10^{-2}$', r'$10^{-1}$', r'$10^0$', r'$10^1$'])
|
|
805
|
+
cbar.ax.set_yticklabels([f"$10^{{{val}}}$" for val in c_ticks])
|
|
806
|
+
# plt.grid(alpha=0.5)
|
|
807
|
+
# plt.title(lm.title)
|
|
808
|
+
# plt.xlabel(lm.xlabel)
|
|
809
|
+
# plt.ylabel(lm.ylabel)
|
|
810
|
+
plt.title(r'Loss per cycle (J/m)')
|
|
811
|
+
plt.xlabel(r'Frequency $f$ (Hz)')
|
|
812
|
+
plt.ylabel(r'Field amplitude $b$ (T)')
|
|
813
|
+
|
|
814
|
+
# plt.annotate(r'Coupling', (np.log10(1.0), np.log10(0.007)), color='white')
|
|
815
|
+
# plt.annotate(r'Filament', (np.log10(0.012), np.log10(0.74)), color='white')
|
|
816
|
+
# plt.annotate(r'(uncoupled)', (np.log10(0.012), np.log10(0.55)), color='white')
|
|
817
|
+
# plt.annotate(r'Filament', (np.log10(45), np.log10(0.38)), color='white')
|
|
818
|
+
# plt.annotate(r'(coupled)', (np.log10(45), np.log10(0.28)), color='white')
|
|
819
|
+
# plt.annotate(r'Eddy', (np.log10(2000), np.log10(0.03)), color='white')
|
|
820
|
+
|
|
821
|
+
# ax.plot(np.log10(0.03), np.log10(0.2), 'o', color='white')#, xytext=(np.log10(0.03), np.log10(0.12)), arrowprops=dict(facecolor='black', shrink=0.02))
|
|
822
|
+
# ax.plot(np.log10(30), np.log10(1), 'o', color='white')#, xytext=(np.log10(40), np.log10(0.8)), arrowprops=dict(facecolor='black', shrink=0.02))
|
|
823
|
+
# ax.plot(np.log10(3), np.log10(0.2), 'o', color='white')#, xytext=(np.log10(2), np.log10(0.2)), arrowprops=dict(facecolor='black', shrink=0.02))
|
|
824
|
+
# ax.plot(np.log10(5000), np.log10(0.2), 'o', color='white')#, xytext=(np.log10(5000), np.log10(0.1)), arrowprops=dict(facecolor='black', shrink=0.02))
|
|
825
|
+
|
|
826
|
+
# ax.annotate('(a)', xy=(np.log10(0.03), np.log10(0.2)), xycoords='data', ha='right', va='bottom', fontsize=20, color='white')
|
|
827
|
+
# ax.annotate('(b)', xy=(np.log10(3), np.log10(0.2)), xycoords='data', ha='right', va='bottom', fontsize=20, color='white')
|
|
828
|
+
# ax.annotate('(c)', xy=(np.log10(30), np.log10(1)), xycoords='data', ha='right', va='bottom', fontsize=20, color='white')
|
|
829
|
+
# ax.annotate('(d)', xy=(np.log10(5000), np.log10(0.2)), xycoords='data', ha='right', va='bottom', fontsize=20, color='white')
|
|
830
|
+
|
|
831
|
+
# Define custom tick labels for x-axis
|
|
832
|
+
x_min_log = int(np.log10(min([eval('sd.' + lm.x_val) for sd in self.simulation_collection])))
|
|
833
|
+
x_max_log = int(np.log10(max([eval('sd.' + lm.x_val) for sd in self.simulation_collection])))
|
|
834
|
+
x = np.arange(x_min_log, x_max_log + 1)
|
|
835
|
+
# Create a list of minor ticks
|
|
836
|
+
minor_x_labels = []
|
|
837
|
+
# 1) Add the ticks from x_min_log to ceil(x_min_log) to the minor_x_test list
|
|
838
|
+
new_ticks = np.linspace(10.0 ** np.floor(x_min_log), 10.0 ** np.ceil(x_min_log), 10)[:-1]
|
|
839
|
+
new_ticks = np.unique(new_ticks[new_ticks >= 10.0 ** x_min_log])
|
|
840
|
+
minor_x_labels.extend(new_ticks)
|
|
841
|
+
# 2) Add the ticks from ceil(x_min_log) to floor(x_max_log) to the minor_x_test list
|
|
842
|
+
for x_val in x:
|
|
843
|
+
new_ticks = np.linspace(10.0 ** x_val, 10.0 ** (x_val + 1), 10)[1:-1]
|
|
844
|
+
if x_val == x[-1]:
|
|
845
|
+
new_ticks = new_ticks[new_ticks <= 10.0 ** x_max_log]
|
|
846
|
+
minor_x_labels.extend(new_ticks)
|
|
847
|
+
minor_x = [np.log10(val) for val in minor_x_labels]
|
|
848
|
+
|
|
849
|
+
new_x_labels = [f"$10^{{{val}}}$" for val in x]
|
|
850
|
+
plt.xticks(x, new_x_labels)
|
|
851
|
+
plt.xticks(minor_x, minor=True)
|
|
852
|
+
|
|
853
|
+
# Define custom tick labels for y-axis
|
|
854
|
+
y_min_log = np.log10(min([eval('sd.' + lm.y_val) for sd in self.simulation_collection]))
|
|
855
|
+
y_max_log = np.log10(max([eval('sd.' + lm.y_val) for sd in self.simulation_collection]))
|
|
856
|
+
y = np.arange(np.ceil(y_min_log), np.floor(y_max_log) + 1)
|
|
857
|
+
# Create a list of minor ticks
|
|
858
|
+
minor_y_labels = []
|
|
859
|
+
# 1) Add the ticks from y_min_log to ceil(y_min_log) to the minor_y_test list
|
|
860
|
+
new_ticks = np.linspace(10.0 ** np.floor(y_min_log), 10.0 ** np.ceil(y_min_log), 10)[:-1]
|
|
861
|
+
new_ticks = np.unique(new_ticks[new_ticks >= 10.0 ** y_min_log])
|
|
862
|
+
minor_y_labels.extend(new_ticks)
|
|
863
|
+
# 2) Add the ticks from ceil(y_min_log) to floor(y_max_log) to the minor_y_test list
|
|
864
|
+
for y_val in y:
|
|
865
|
+
new_ticks = np.linspace(10.0 ** y_val, 10.0 ** (y_val + 1), 10)[1:-1]
|
|
866
|
+
if y_val == y[-1]:
|
|
867
|
+
new_ticks = new_ticks[new_ticks <= 10.0 ** y_max_log]
|
|
868
|
+
minor_y_labels.extend(new_ticks)
|
|
869
|
+
|
|
870
|
+
new_y_labels = [f"$10^{{{int(val)}}}$" for val in y]
|
|
871
|
+
minor_y = [np.log10(val) for val in minor_y_labels]
|
|
872
|
+
plt.yticks(y, new_y_labels)
|
|
873
|
+
plt.yticks(minor_y, minor=True)
|
|
874
|
+
|
|
875
|
+
# plt.savefig('C:/Users/jdular/cernbox/Documents/Reports/CERN_Reports/linkedFluxPaper/fig/loss_map_54fil_noI.pdf', bbox_inches='tight')
|
|
876
|
+
|
|
877
|
+
if lm.save_plot:
|
|
878
|
+
filePath = self.create_non_overwriting_filepath(self.outputs_folder_path, lm.filename, '.pdf', self.fdm.run.overwrite)
|
|
879
|
+
plt.savefig(filePath, bbox_inches='tight')
|
|
880
|
+
|
|
881
|
+
if self.fdm.run.launch_gui: plt.show()
|
|
882
|
+
|
|
883
|
+
def create_errorMap(self, flux_model, display=True, iteration=None):
|
|
884
|
+
""" This function creates a error map for a given rohf model on the space spanned by the simulations (f, I_amp) """
|
|
885
|
+
|
|
886
|
+
error_type = self.fdm.magnet.postproc.batch_postproc.rohf_on_grid.error_type
|
|
887
|
+
|
|
888
|
+
N = len(self.simulation_collection)
|
|
889
|
+
frequencies = np.zeros(N)
|
|
890
|
+
peakCurrents = np.zeros(N)
|
|
891
|
+
pc_error = np.zeros(N)
|
|
892
|
+
dyn_error = np.zeros(N)
|
|
893
|
+
flux_error = np.zeros(N)
|
|
894
|
+
for idx, simulation in enumerate(self.simulation_collection):
|
|
895
|
+
frequencies[idx] = simulation.f
|
|
896
|
+
peakCurrents[idx] = simulation.I_amp
|
|
897
|
+
# compute rohf metrics
|
|
898
|
+
rohf_flux, rohf_dyn_loss, _, rohf_pc_loss, _ = flux_model.compute(simulation.time, simulation.I_transport, sim=simulation)
|
|
899
|
+
# (relative) per cycle loss error
|
|
900
|
+
pc_error[idx] = abs(rohf_pc_loss - simulation.total_power_per_cycle['FilamentLoss']) / simulation.total_power_per_cycle['FilamentLoss']
|
|
901
|
+
# (relative) mean dynamic error
|
|
902
|
+
dyn_error[idx] = np.mean(abs((rohf_dyn_loss - simulation.power['FilamentLoss']) / simulation.power['FilamentLoss']))
|
|
903
|
+
# (relative) mean flux error
|
|
904
|
+
non_zero_idx = np.nonzero(simulation.flux_internal)
|
|
905
|
+
flux_error[idx] = np.mean(abs((rohf_flux[non_zero_idx] - simulation.flux_internal[non_zero_idx]) / simulation.flux_internal[non_zero_idx]))
|
|
906
|
+
|
|
907
|
+
if error_type == 'pc_loss':
|
|
908
|
+
error = pc_error
|
|
909
|
+
elif error_type == 'flux':
|
|
910
|
+
error = flux_error
|
|
911
|
+
elif error_type == 'dyn_loss':
|
|
912
|
+
error = dyn_error
|
|
913
|
+
else:
|
|
914
|
+
raise ValueError('Unrecognized error type')
|
|
915
|
+
|
|
916
|
+
# plot errorMap result
|
|
917
|
+
if display:
|
|
918
|
+
|
|
919
|
+
show_descriptions = False
|
|
920
|
+
freq_i, curr_i = np.linspace(min(frequencies), max(frequencies), 1000), np.linspace(min(peakCurrents), max(peakCurrents), 1000)
|
|
921
|
+
freq_i, curr_i = np.meshgrid(freq_i, curr_i)
|
|
922
|
+
plt.figure()
|
|
923
|
+
ax = plt.gca()
|
|
924
|
+
title = str(error_type) + " map"
|
|
925
|
+
if show_descriptions:
|
|
926
|
+
ax.set_title(title)
|
|
927
|
+
ax.set_xlabel(r'Frequency $f$ ($\mathrm{Hz}$)')
|
|
928
|
+
ax.set_ylabel(r"Transport current ratio $I/I_\mathrm{c}$")
|
|
929
|
+
else:
|
|
930
|
+
ax.set_axis_off()
|
|
931
|
+
|
|
932
|
+
if self.fdm.magnet.postproc.batch_postproc.rohf_on_grid.interpolate_error_map:
|
|
933
|
+
rbf = interpolate.Rbf(frequencies, peakCurrents, error, function='linear')
|
|
934
|
+
error_i = rbf(freq_i, curr_i)
|
|
935
|
+
plt.imshow(error_i, vmin=error.min(), vmax=error.max(), origin='lower', aspect='auto',
|
|
936
|
+
extent=[min(frequencies), max(frequencies), min(peakCurrents) / max(peakCurrents), 1])
|
|
937
|
+
plt.scatter(frequencies, peakCurrents / max(peakCurrents), c=error, edgecolor='w')
|
|
938
|
+
ax.set_xscale('log')
|
|
939
|
+
# ax.set_yscale('log')
|
|
940
|
+
plt.colorbar()
|
|
941
|
+
plt.show()
|
|
942
|
+
else:
|
|
943
|
+
# cumulative error over map
|
|
944
|
+
return sum(error)
|
|
945
|
+
|
|
946
|
+
def plot2d(self):
|
|
947
|
+
"""
|
|
948
|
+
This function is used to create a 2d plot. It is supposed to be flexible and work for various kinds of plots one may want to create.
|
|
949
|
+
"""
|
|
950
|
+
|
|
951
|
+
def apply_plot_settings():
|
|
952
|
+
if ref:
|
|
953
|
+
plt.plot(ref_array[0], ref_array[1], linestyle='--', label=self.fdm.magnet.postproc.batch_postproc.plot2d.reference_label)
|
|
954
|
+
if self.fdm.magnet.postproc.batch_postproc.plot2d.legend:
|
|
955
|
+
plt.legend()
|
|
956
|
+
plt.grid(linestyle='dotted')
|
|
957
|
+
plt.xlabel(self.fdm.magnet.postproc.batch_postproc.plot2d.xlabel)
|
|
958
|
+
plt.ylabel(self.fdm.magnet.postproc.batch_postproc.plot2d.ylabel)
|
|
959
|
+
plt.title(title)
|
|
960
|
+
if self.fdm.magnet.postproc.batch_postproc.plot2d.x_log:
|
|
961
|
+
plt.xscale('log')
|
|
962
|
+
if self.fdm.magnet.postproc.batch_postproc.plot2d.y_log:
|
|
963
|
+
plt.yscale('log')
|
|
964
|
+
|
|
965
|
+
if self.tex_installation:
|
|
966
|
+
plt.rcParams['text.usetex'] = True
|
|
967
|
+
plt.rcParams['font.family'] = 'times'
|
|
968
|
+
# plt.rcParams['font.size'] = 20
|
|
969
|
+
|
|
970
|
+
plotdata_list = []
|
|
971
|
+
# use labelcount to subdevide simulations into sets of equal length
|
|
972
|
+
labellen = int(len(self.fdm.magnet.postproc.batch_postproc.plot2d.labels))
|
|
973
|
+
grplen = int(len(self.simulation_collection) / labellen)
|
|
974
|
+
|
|
975
|
+
# Create the title (or titles if combined_plot is False)
|
|
976
|
+
title = self.fdm.magnet.postproc.batch_postproc.plot2d.title
|
|
977
|
+
if self.fdm.magnet.postproc.batch_postproc.plot2d.combined_plot:
|
|
978
|
+
sd = self.simulation_collection[0]
|
|
979
|
+
commands = re.findall('<<(.*?)>>', title)
|
|
980
|
+
for c in commands:
|
|
981
|
+
title = title.replace(f"<<{c}>>", str(eval('sd.' + c)))
|
|
982
|
+
else:
|
|
983
|
+
titles = []
|
|
984
|
+
for sd in self.simulation_collection:
|
|
985
|
+
commands = re.findall('<<(.*?)>>', title)
|
|
986
|
+
title_i = title
|
|
987
|
+
for c in commands:
|
|
988
|
+
title_i = title_i.replace(f"<<{c}>>", str(eval('sd.' + c)))
|
|
989
|
+
titles.append(title_i)
|
|
990
|
+
|
|
991
|
+
# colors = plt.cm.get_cmap('magma').resampled(len(self.simulation_collection)).colors
|
|
992
|
+
max_len = max([len(self.simulation_collection), len(self.fluxmodel_collection)]) if self.fluxmodel_collection else len(self.simulation_collection)
|
|
993
|
+
colors = plt.cm.get_cmap('viridis').resampled(max_len).colors
|
|
994
|
+
|
|
995
|
+
# convert selected simulation parameters into list of PlotData
|
|
996
|
+
simulation_idx = [] # nested list with indices
|
|
997
|
+
fluxmodel_idx = []
|
|
998
|
+
style_cycle = ['--', ':', '-.']
|
|
999
|
+
for i, simulation in enumerate(self.simulation_collection):
|
|
1000
|
+
# get x_data
|
|
1001
|
+
x_data = eval(self.fdm.magnet.postproc.batch_postproc.plot2d.x_val)
|
|
1002
|
+
# base label
|
|
1003
|
+
if len(self.fdm.magnet.postproc.batch_postproc.plot2d.labels) == len(self.simulation_collection):
|
|
1004
|
+
label_idx = i
|
|
1005
|
+
else:
|
|
1006
|
+
label_idx = 0
|
|
1007
|
+
label_cmd = re.findall('<<(.*?)>>', self.fdm.magnet.postproc.batch_postproc.plot2d.labels[label_idx])
|
|
1008
|
+
sim_label = self.fdm.magnet.postproc.batch_postproc.plot2d.labels[label_idx].replace(f"<<{label_cmd}>>", str(eval('simulation.' + label_cmd))) if label_cmd else self.fdm.magnet.postproc.batch_postproc.plot2d.labels[label_idx]
|
|
1009
|
+
# add (multiple) y_data sets w. label
|
|
1010
|
+
for j in range(len(self.fdm.magnet.postproc.batch_postproc.plot2d.y_vals)):
|
|
1011
|
+
y_data = eval(self.fdm.magnet.postproc.batch_postproc.plot2d.y_vals[j])
|
|
1012
|
+
|
|
1013
|
+
sublabel = sim_label if len(self.fdm.magnet.postproc.batch_postproc.plot2d.y_vals) == 1 else sim_label + ' (' + str(i) + ')'
|
|
1014
|
+
plotdata_list.append(PlotData(x_data=x_data, y_data=y_data, label=sublabel, color=colors[i], linestyle='-'))
|
|
1015
|
+
simulation_idx.append(len(plotdata_list) - 1)
|
|
1016
|
+
# add multiple fluxmodel results
|
|
1017
|
+
if self.fluxmodel_collection:
|
|
1018
|
+
for k, fluxmodel in enumerate(self.fluxmodel_collection):
|
|
1019
|
+
y_data = eval(self.fdm.magnet.postproc.batch_postproc.plot2d.y_val_fluxmodel)
|
|
1020
|
+
plotdata_list.append(PlotData(x_data=x_data, y_data=y_data, label=fluxmodel.label + '_' + str(sim_label), color=colors[i], linestyle=style_cycle[k % len(style_cycle)]))
|
|
1021
|
+
fluxmodel_idx.append(len(plotdata_list) - 1)
|
|
1022
|
+
|
|
1023
|
+
# Load reference - if defined
|
|
1024
|
+
ref = self.fdm.magnet.postproc.batch_postproc.plot2d.reference_vals
|
|
1025
|
+
ref_array = []
|
|
1026
|
+
if ref:
|
|
1027
|
+
for i, ref_val in enumerate(ref):
|
|
1028
|
+
commands = re.findall('<<(.*?)>>', ref_val)
|
|
1029
|
+
for c in commands:
|
|
1030
|
+
ref_val = ref_val.replace(f"<<{c}>>", str('sd.' + c))
|
|
1031
|
+
ref_array.append(eval(ref_val))
|
|
1032
|
+
|
|
1033
|
+
# Create result folder:
|
|
1034
|
+
if not self.fdm.magnet.postproc.batch_postproc.plot2d.combined_plot and self.fdm.magnet.postproc.batch_postproc.plot2d.save_plot:
|
|
1035
|
+
# Create a folder to save the plots if combined_plot is False and save_plot is True:
|
|
1036
|
+
filename = self.fdm.magnet.postproc.batch_postproc.plot2d.filename
|
|
1037
|
+
folder_path = self.create_non_overwriting_filepath(self.outputs_folder_path, filename, '', self.fdm.run.overwrite)
|
|
1038
|
+
if not os.path.exists(folder_path): os.makedirs(folder_path)
|
|
1039
|
+
|
|
1040
|
+
# plot data as continous graphs in one plot if there are no scalars
|
|
1041
|
+
scalar_plotdata = any([hasattr(plotdata.x_data, "__len__") for plotdata in plotdata_list])
|
|
1042
|
+
if not scalar_plotdata:
|
|
1043
|
+
for grp_idx, grp_plotdata in enumerate([plotdata_list[i::labellen] for i in range(labellen)]):
|
|
1044
|
+
plt.plot([data.x_data for data in grp_plotdata], [data.y_data for data in grp_plotdata], self.fdm.magnet.postproc.batch_postproc.plot2d.linestyle, label=self.fdm.magnet.postproc.batch_postproc.plot2d.labels[grp_idx], color=colors[-grp_idx])
|
|
1045
|
+
if not self.fdm.magnet.postproc.batch_postproc.plot2d.combined_plot:
|
|
1046
|
+
plt.figure()
|
|
1047
|
+
if self.fluxmodel_collection:
|
|
1048
|
+
for grp_idx, group in enumerate([fluxmodel_idx[i:i + grplen] for i in range(0, len(fluxmodel_idx), grplen)]):
|
|
1049
|
+
plt.plot([plotdata_list[idx].x_data for idx in group], [plotdata_list[idx].y_data for idx in group], '-o', label="ROHF", color='red')
|
|
1050
|
+
apply_plot_settings()
|
|
1051
|
+
# plot data as points in one graph each in one plot
|
|
1052
|
+
else:
|
|
1053
|
+
print('test')
|
|
1054
|
+
for idx, plotdata in enumerate(plotdata_list):
|
|
1055
|
+
plt.plot(plotdata.x_data, plotdata.y_data, linestyle=plotdata.linestyle, label=plotdata.label, color=plotdata.color)
|
|
1056
|
+
apply_plot_settings()
|
|
1057
|
+
if self.fdm.magnet.postproc.batch_postproc.plot2d.save_plot:
|
|
1058
|
+
filename = self.fdm.magnet.postproc.batch_postproc.plot2d.filename
|
|
1059
|
+
filePath = self.create_non_overwriting_filepath(self.outputs_folder_path, filename, '.png', self.fdm.run.overwrite)
|
|
1060
|
+
plt.savefig(filePath, dpi=300)
|
|
1061
|
+
|
|
1062
|
+
if self.fdm.magnet.postproc.batch_postproc.plot2d.combined_plot and self.fdm.magnet.postproc.batch_postproc.plot2d.save_pgfdata:
|
|
1063
|
+
if not scalar_plotdata:
|
|
1064
|
+
data_tuple = ()
|
|
1065
|
+
data_names = []
|
|
1066
|
+
for grp_idx, grp_plotdata in enumerate([plotdata_list[i::labellen] for i in range(labellen)]):
|
|
1067
|
+
data_tuple = data_tuple + ([data.x_data for data in grp_plotdata], [data.y_data for data in grp_plotdata])
|
|
1068
|
+
data_names.extend([self.fdm.magnet.postproc.batch_postproc.plot2d.labels[grp_idx] + str("_x"), self.fdm.magnet.postproc.batch_postproc.plot2d.labels[grp_idx] + str("_y")])
|
|
1069
|
+
data = np.vstack(data_tuple).T
|
|
1070
|
+
header = " ".join(data_names)
|
|
1071
|
+
else:
|
|
1072
|
+
max_len = max([len(plotdata.x_data) for plotdata in plotdata_list])
|
|
1073
|
+
# pad shorter data columns with nan - is ignored by pgfplots
|
|
1074
|
+
data = np.vstack(tuple([np.pad(plotdata.x_data, (0, max_len - len(plotdata.x_data)), "constant", constant_values=(np.nan,)) for plotdata in plotdata_list]
|
|
1075
|
+
+ [np.pad(plotdata.y_data, (0, max_len - len(plotdata.y_data)), "constant", constant_values=(np.nan,)) for plotdata in plotdata_list])).T
|
|
1076
|
+
header = " ".join([plotdata.label + '_x' for plotdata in plotdata_list]) + " " + " ".join([plotdata.label + '_y' for plotdata in plotdata_list])
|
|
1077
|
+
np.savetxt(os.path.join(self.outputs_folder_path, 'pgfdata.txt'), data, header=header, comments="")
|
|
1078
|
+
|
|
1079
|
+
if self.fdm.run.launch_gui: plt.show()
|