Repository navigation
Expand file tree
/
Copy pathbuild.py
More file actions
1545 lines (1394 loc) · 58.1 KB
/
Copy pathbuild.py
File metadata and controls
1545 lines (1394 loc) · 58.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Module containing routines for build calculations"""
import logging
from dataclasses import dataclass
from enum import IntEnum, unique
import numpy as np
from tabulate import tabulate
from process.core import constants
from process.core import process_output as po
from process.core.exceptions import ProcessValueError
from process.core.model import Model
from process.data_structure.build_variables import (
CSPrecompressionConfiguration,
TFCSRadialConfiguration,
)
from process.data_structure.physics_variables import DivertorNumberModels
from process.models.physics.current_drive import (
CurrentDriveMethodType,
CurrentDriveModel,
)
from process.models.tfcoil.base import TFCoilShapeModel, TFConductorModel
from process.models.tfcoil.superconducting import SuperconductingTFWPShapeType
logger = logging.getLogger(__name__)
@dataclass
class RZPoint:
"""A generic container for data that has r and z components"""
r: float
z: float
@unique
class FwBlktVVShape(IntEnum):
"""Enum for first wall, blanket, and vacuum vessel shape options."""
D_SHAPED = 1
ELLIPTICAL_SHAPED = 2
class Build(Model):
"""Routines for build calculations"""
def __init__(self):
self.outfile = constants.NOUT
self.mfile = constants.MFILE
def output(self):
"""Output the build information"""
# Radial build
self.calculate_radial_build(output=True)
# Vertical build
self.calculate_vertical_build(output=True)
def run(self):
"""Run the build routines"""
self.calculate_radial_build(output=False)
self.calculate_vertical_build(output=False)
(
self.data.current_drive.radius_beam_tangency,
self.data.current_drive.radius_beam_tangency_max,
) = self.calculate_beam_port_size(
f_radius_beam_tangency_rmajor=self.data.current_drive.f_radius_beam_tangency_rmajor,
rmajor=self.data.physics.rmajor,
n_tf_coils=self.data.tfcoil.n_tf_coils,
dx_tf_inboard_out_toroidal=self.data.tfcoil.dx_tf_inboard_out_toroidal,
dr_tf_outboard=self.data.build.dr_tf_outboard,
r_tf_outboard_mid=self.data.build.r_tf_outboard_mid,
dx_beam_duct=self.data.current_drive.dx_beam_duct,
dx_beam_shield=self.data.current_drive.dx_beam_shield,
)
@staticmethod
def calculate_beam_port_size(
f_radius_beam_tangency_rmajor: float,
rmajor: float,
n_tf_coils: int,
dx_tf_inboard_out_toroidal: float,
dr_tf_outboard: float,
r_tf_outboard_mid: float,
dx_beam_duct: float,
dx_beam_shield: float,
) -> tuple[float, float]:
"""Calculates the maximum possible tangency radius for adequate beam access.
Parameters
----------
f_radius_beam_tangency_rmajor : float
Fraction of rmajor for beam tangency
rmajor : float
Major radius
n_tf_coils : int
Number of TF coils
dx_tf_inboard_out_toroidal : float
Toroidal width of outboard TF coil
dr_tf_outboard : float
Radial thickness of outboard TF coil leg
r_tf_outboard_mid : float
Major radius of centre of outboard TF coil
dx_beam_duct : float
Width of beam duct
dx_beam_shield : float
Shielding width on both sides of beam duct
Returns
-------
tuple[float, float]
Tuple containing (radius_beam_tangency, radius_beam_tangency_max)
"""
# Have kept the single letter variable names to match the original code and
# documentation diagram.
radius_beam_tangency = f_radius_beam_tangency_rmajor * rmajor
omega = 2.0 * np.pi / n_tf_coils
a = 0.5e0 * dx_tf_inboard_out_toroidal
if np.isinf(a):
logger.error("a is inf. Kludging to 1e10.")
a = 1e10
b = dr_tf_outboard
if np.isinf(b):
logger.error("b is inf. Kludging to 1e10.")
b = 1e10
c = dx_beam_duct + 2.0e0 * dx_beam_shield
d = r_tf_outboard_mid - 0.5e0 * b
if np.isinf(d):
logger.error("d is inf. Kludging to 1e10.")
d = 1e10
e = np.sqrt(a**2 + (d + b) ** 2)
f = np.sqrt(a**2 + d**2)
theta = omega - np.arctan(a / d)
phi = theta - np.arcsin(a / e)
g = np.sqrt(e**2 + f**2 - 2.0e0 * e * f * np.cos(phi))
if g > c:
h = np.sqrt(g**2 - c**2)
alpha = np.arctan(h / c)
eps = np.arcsin(e * np.sin(phi) / g) - alpha
radius_beam_tangency_max = f * np.cos(eps) - 0.5e0 * c
else:
logger.error(
"Max beam tangency radius set =0 temporarily; "
"change dx_beam_duct. %s %s",
g,
c,
)
radius_beam_tangency_max = 0.0e0
return radius_beam_tangency, radius_beam_tangency_max
def _vertical_build_out(self, i_single_null: DivertorNumberModels):
bld = self.data.build
sn = i_single_null == i_single_null.SINGLE_NULL
po.ocmmnt(self.outfile, f"{'Single' if sn else 'Double'} null case")
bld.dz_vv_upper = 0.5 * (bld.dz_vv_upper + bld.dz_vv_lower)
bld.dz_fw_upper = 0.5 * (bld.dr_fw_inboard + bld.dr_fw_outboard)
vbuild = (
self.data.buildings.dz_tf_cryostat
+ bld.dr_tf_inboard
+ bld.dr_tf_shld_gap
+ bld.dz_shld_thermal
+ bld.dz_shld_vv_gap
+ bld.dz_vv_upper
+ (bld.dr_shld_blkt_gap if sn else 0)
+ bld.dz_shld_upper
+ (bld.dz_blkt_upper if sn else 0)
+ (
(bld.dz_fw_upper + bld.dz_fw_plasma_gap)
if sn
else (
self.data.divertor.dz_divertor + self.data.build.dz_xpoint_divertor
)
)
+ bld.z_plasma_xpoint_upper
)
# To calculate vertical offset between TF coil centre and plasma centre
vbuile1 = vbuild
dz_fw_upper = 0.5e0 * (bld.dr_fw_inboard + bld.dr_fw_outboard)
# Top of TF coil
tf_top = vbuild - self.data.buildings.dz_tf_cryostat
top = (
(
("Top blanket", bld.dz_blkt_upper, "(dz_blkt_upper)"),
("Top first wall", dz_fw_upper, "(dz_fw_upper)"),
)
if sn
else (
(
"Divertor structure",
self.data.divertor.dz_divertor,
"(dz_divertor)",
),
)
)
vbuild, vbuild_top_table = self.create_vbuild_table(
vbuild=vbuild,
entry=[
(
"Cryostat roof structure*",
self.data.buildings.dz_tf_cryostat,
"(dz_tf_cryostat)",
),
("TF coil", bld.dr_tf_inboard, "(dr_tf_inboard)"),
("Gap", bld.dr_tf_shld_gap, "(dr_tf_shld_gap)"),
("Thermal shield, vertical", bld.dz_shld_thermal, "(dz_shld_thermal)"),
("Gap", bld.dz_shld_vv_gap, "(dz_shld_vv_gap)"),
(
"Vacuum vessel (and shielding)",
bld.dz_vv_upper + bld.dz_shld_upper,
"(dz_vv_upper+dz_shld_upper)",
),
("Gap", bld.dr_shld_blkt_gap, "(dr_shld_blkt_gap)"),
*top,
("Top scrape-off", bld.dz_fw_plasma_gap, "(dz_fw_plasma_gap)")
if sn
else (
"Top scrape-off",
self.data.build.dz_xpoint_divertor,
"(dz_xpoint_divertor)",
),
(
"Plasma upper X-point height (m)",
bld.z_plasma_xpoint_upper,
"(z_plasma_xpoint_upper)",
),
],
)
top = (
(
(
"Top blanket vertical thickness (m)",
"(dz_blkt_upper)",
bld.dz_blkt_upper,
),
("Top first wall vertical thickness (m)", "(dz_fw_upper)", dz_fw_upper),
)
if sn
else (
(
"Divertor structure vertical thickness (m)",
"(dz_divertor)",
self.data.divertor.dz_divertor,
),
)
)
for desc, name, val in [
(
"Cryostat roof structure*",
"(dz_tf_cryostat)",
self.data.buildings.dz_tf_cryostat,
),
("Thermal shield, vertical (m)", "(dz_shld_thermal)", bld.dz_shld_thermal),
(
"Vessel - TF coil vertical gap (m)",
"(dz_shld_vv_gap)",
bld.dz_shld_vv_gap,
),
(
"Topside vacuum vessel radial thickness (m)",
"(dz_vv_upper)",
bld.dz_vv_upper,
),
(
"Top radiation shield thickness (m)",
"(dz_shld_upper)",
bld.dz_shld_upper,
),
*top,
(
"Top scrape-off vertical thickness (m)",
"(dz_fw_plasma_gap)",
bld.dz_fw_plasma_gap,
)
if sn
else (
"Top scrape-off vertical thickness (m)",
"(dz_xpoint_divertor)",
self.data.build.dz_xpoint_divertor,
),
(
"Plasma upper X-point height (m)",
"(z_plasma_xpoint_upper)",
bld.z_plasma_xpoint_upper,
),
]:
po.ovarre(self.mfile, desc, name, val)
vbuild_table = [*vbuild_top_table]
vbuild_table.append(("Midplane", 0.0, vbuild, ""))
vbuild, vbuild_bottom_table = self.create_vbuild_table(
vbuild=vbuild,
entry=[
(
"Plasma lower X-point height (m)",
bld.z_plasma_xpoint_lower,
"(z_plasma_xpoint_lower)",
),
("Lower scrape-off", bld.dz_xpoint_divertor, "(dz_xpoint_divertor)"),
("Divertor structure", self.data.divertor.dz_divertor, "(dz_divertor)"),
(
"Vacuum vessel (and shielding)",
bld.dz_vv_lower + bld.dz_shld_lower,
"(dz_vv_lower+dz_shld_lower)",
),
("Gap", bld.dz_shld_vv_gap, "(dz_shld_vv_gap)"),
("Thermal shield, vertical", bld.dz_shld_thermal, "(dz_shld_thermal)"),
("Gap", bld.dr_tf_shld_gap, "(dr_tf_shld_gap)"),
("TF coil", bld.dr_tf_inboard, "(dr_tf_inboard)"),
(
"Cryostat floor structure**",
self.data.buildings.dz_tf_cryostat,
"(dz_tf_cryostat)",
),
],
before=True,
)
vbuild_table.extend(vbuild_bottom_table)
for desc, name, val in [
(
"Plasma lower X-point height (m)",
"(z_plasma_xpoint_lower)",
bld.z_plasma_xpoint_lower,
),
(
"Bottom scrape-off vertical thickness (m)",
"(dz_xpoint_divertor)",
bld.dz_xpoint_divertor,
),
(
"Divertor structure vertical thickness (m)",
"(dz_divertor)",
self.data.divertor.dz_divertor,
),
(
"Bottom radiation shield thickness (m)",
"(dz_shld_lower)",
bld.dz_shld_lower,
),
(
"Underside vacuum vessel radial thickness (m)",
"(dz_vv_lower)",
bld.dz_vv_lower,
),
]:
po.ovarre(self.mfile, desc, name, val)
po.write(
self.outfile,
tabulate(
vbuild_table,
headers=[
"Description",
"Component height",
"Cumulative height",
"Variable name",
],
floatfmt=".3e",
),
)
# Total height of TF coil
tf_height = tf_top - vbuild + self.data.buildings.dz_tf_cryostat
# Inner vertical dimension of TF coil
bld.dh_tf_inner_bore = tf_height - 2 * bld.dr_tf_inboard
# To calculate vertical offset between TF coil centre and plasma centre
bld.dz_tf_plasma_centre_offset = (vbuile1 + vbuild) / 2.0e0
@staticmethod
def create_vbuild_table(
vbuild: float, entry: list[tuple[str, float, str]], *, before=False
):
"""Write obuild entry
Parameters
----------
vbuild:
vertical build current point
entry:
the new entries to be added to be vertical build
before:
whether to remove the variable value from the current entry before
or after recording the vertical build value.
Top half of the machine set to False, lower half set to True
"""
table = []
for desc, var, name in entry:
if before:
vbuild -= var
table.append((desc, var, vbuild, name))
if not before:
vbuild -= var
return vbuild, table
def calculate_vertical_build(self, output: bool):
"""Determines the vertical build of the machine.
This method calculates various parameters related
to the vertical build of the machine,
such as thicknesses, radii, and areas.
Results can be outputted with the `output` flag.
Parameters
----------
output : bool
Flag indicating whether to output results
Raises
------
ProcessValueError
Only 1 or 2 divertors are supported
"""
if self.data.divertor.n_divertors not in {1, 2}:
raise ProcessValueError(
f"n_divertors = {self.data.divertor.n_divertors} is invalid. "
"Only 1 or 2 divertors are supported."
)
# Set the X-point heights for the top and bottom of the plasma
# Assumes top-down plasma symmetry
self.data.build.z_plasma_xpoint_upper = (
self.data.physics.rminor * self.data.physics.kappa
)
self.data.build.z_plasma_xpoint_lower = (
self.data.physics.rminor * self.data.physics.kappa
)
i_single_null = DivertorNumberModels(self.data.physics.i_single_null)
if output:
po.oheadr(self.outfile, "Vertical Build")
po.ovarre(
self.mfile,
"Divertor null switch",
"(i_single_null)",
self.data.physics.i_single_null,
)
self._vertical_build_out(i_single_null)
po.ovarre(
self.mfile,
"Ratio of Central Solenoid height to TF coil internal height",
"(f_z_cs_tf_internal)",
self.data.pf_coil.f_z_cs_tf_internal,
)
po.ocmmnt(
self.outfile,
"\n*Cryostat roof allowance includes uppermost PF coil and "
"outer thermal shield.\n**Cryostat floor allowance "
"includes lowermost PF coil, outer thermal shield and gravity support.",
)
# Output the cdivertor geometry
divht = self.divgeom(output)
# Issue #481 Remove self.data.build.vgaptf
if self.data.build.dz_xpoint_divertor < 0.00001e0:
self.data.build.dz_xpoint_divertor = divht
# If self.data.build.dz_xpoint_divertor /= 0 use the value set by the user.
# Height to inside edge of TF coil. TF coils are assumed to be symmetrical.
# Therefore this applies to single and double null cases.
self.data.build.z_tf_inside_half = (
self.data.build.z_plasma_xpoint_upper
+ self.data.build.dz_xpoint_divertor
+ self.data.divertor.dz_divertor
+ self.data.build.dz_shld_lower
+ self.data.build.dz_vv_lower
+ self.data.build.dz_shld_vv_gap
+ self.data.build.dz_shld_thermal
+ self.data.build.dr_tf_shld_gap
)
# Vertical locations of divertor coils
match DivertorNumberModels(i_single_null):
case DivertorNumberModels.DOUBLE_NULL:
self.data.build.z_tf_top = (
self.data.build.z_tf_inside_half + self.data.build.dr_tf_inboard
)
self.data.build.dz_tf_upper_lower_midplane = 0.0e0
case DivertorNumberModels.SINGLE_NULL:
self.data.build.z_tf_top = (
self.data.build.dr_tf_inboard
+ self.data.build.dr_tf_shld_gap
+ self.data.build.dz_shld_thermal
+ self.data.build.dz_shld_vv_gap
+ self.data.build.dz_vv_upper
+ self.data.build.dz_shld_upper
+ self.data.build.dr_shld_blkt_gap
+ self.data.build.dz_blkt_upper
+ 0.5e0
* (self.data.build.dr_fw_inboard + self.data.build.dr_fw_outboard)
+ self.data.build.dz_fw_plasma_gap
+ self.data.build.z_plasma_xpoint_upper
)
self.data.build.dz_tf_upper_lower_midplane = self.data.build.z_tf_top - (
self.data.build.z_tf_inside_half + self.data.build.dr_tf_inboard
)
def divgeom(self, output: bool):
"""Divertor geometry calculation
This subroutine determines the divertor geometry.
The inboard (i) and outboard (o) plasma surfaces
are approximated by arcs, and followed past the X-point to
determine the maximum height.
TART option: Peng SOFT paper
Parameters
----------
output: bool
Returns
-------
divht:
divertor height (m)
"""
if self.data.physics.itart == 1:
return 1.75e0 * self.data.physics.rminor
# Conventional tokamak divertor model
# options for separate upper and lower self.data.physics.triangularity
kap = self.data.physics.kappa
triu = tril = self.data.physics.triang
# Find radius of inner and outer plasma arcs
def rc(trilio):
"""Radius of plasma arcs"""
return 0.5 * np.sqrt(
(self.data.physics.rminor**2 * (trilio**2 + kap**2) ** 2) / (trilio**2)
)
rco = rc(tril + 1)
rci = rc(tril - 1)
def theta(trilio, rcio):
"""Find angle between vertical and legs"""
return np.arcsin(1 - (self.data.physics.rminor * trilio) / rcio)
# Inboard arc angle = outboard leg angle
thetai = theta(1 + tril, rco)
# Outboard arc angle = inboard leg angle
thetao = theta(1 - tril, rci)
# Position of lower x-pt
rxpt = self.data.physics.rmajor - tril * self.data.physics.rminor
zxpt = -1.0e0 * kap * self.data.physics.rminor
# Position of outer strike point
self.data.build.rspo = rxpt + self.data.build.plsepo * np.cos(thetao)
zspo = zxpt - self.data.build.plsepo * np.sin(thetao)
# Position of inner strike point
rspi = rxpt - self.data.build.plsepi * np.cos(thetai)
zspi = zxpt - self.data.build.plsepi * np.sin(thetai)
# Position of inner plate ends
def plate_cos(l_div_plate, theta, beta):
"""Plate cos of angle"""
return (l_div_plate / 2) * np.cos(theta + beta)
def plate_sin(l_div_plate, theta, beta):
"""Plate sin of angle"""
return (l_div_plate / 2) * np.sin(theta + beta)
inner_plte_cos = plate_cos(
self.data.build.plleni, thetai, self.data.divertor.betai
)
inner_plte_sin = plate_sin(
self.data.build.plleni, thetai, self.data.divertor.betai
)
outer_plte_cos = plate_cos(
self.data.build.plleno, thetao, self.data.divertor.betao
)
outer_plte_sin = plate_sin(
self.data.build.plleno, thetao, self.data.divertor.betao
)
# Inner and Outer plate top and bottom vertical coordinates
zplti = zspi + inner_plte_sin
zplbi = zspi - inner_plte_sin
zplto = zspo + outer_plte_sin
zplbo = zspo - outer_plte_sin
divht = max(zplti, zplto) - min(zplbo, zplbi)
if output:
self.divertor_geom_output(
divht=divht,
tril=tril,
triu=triu,
thetai=thetai,
thetao=thetao,
rco=rco,
rci=rci,
xpt=RZPoint(r=rxpt, z=zxpt),
spi=RZPoint(r=rspi, z=zspi),
zspo=zspo,
# Position of inner strike points
plti=RZPoint(r=rspi + inner_plte_cos, z=zplti),
plbi=RZPoint(r=rspi - inner_plte_cos, z=zplbi),
# Position of outer plate ends
plto=RZPoint(r=self.data.build.rspo - outer_plte_cos, z=zplto),
plbo=RZPoint(r=self.data.build.rspo + outer_plte_cos, z=zplbo),
)
return divht
def divertor_geom_output(
self,
divht,
tril,
triu,
thetai,
thetao,
rco,
rci,
xpt,
spi,
zspo,
plti,
plbi,
plto,
plbo,
):
"""Divertor geometry output"""
po.oheadr(self.outfile, "Divertor build and plasma position")
if self.data.divertor.n_divertors == 1:
po.ocmmnt(self.outfile, "Divertor Configuration = Single Null Divertor")
po.oblnkl(self.outfile)
for desc, name, var in [
(
"Plasma top position, radial (m)",
"(ptop_radial)",
self.data.physics.rmajor - triu * self.data.physics.rminor,
),
(
"Plasma top position, vertical (m)",
"(ptop_vertical)",
self.data.physics.kappa * self.data.physics.rminor,
),
(
"Plasma geometric centre, radial (m)",
"(rmajor)",
self.data.physics.rmajor,
),
("Plasma geometric centre, vertical (m)", "(0.0)", 0.0e0),
("Plasma lower triangularity", "(tril)", tril),
("Plasma elongation", "(kappa)", self.data.physics.kappa),
(
"TF coil vertical offset (m)",
"(dz_tf_plasma_centre_offset)",
self.data.build.dz_tf_plasma_centre_offset,
),
("Plasma outer arc radius of curvature (m)", "(rco)", rco),
("Plasma inner arc radius of curvature (m)", "(rci)", rci),
("Plasma lower X-pt, radial (m)", "(rxpt)", xpt.r),
("Plasma lower X-pt, vertical (m)", "(zxpt)", xpt.z),
(
"Poloidal plane angle between vertical and inner leg (rad)",
"(thetai)",
thetai,
),
(
"Poloidal plane angle between vertical and outer leg (rad)",
"(thetao)",
thetao,
),
(
"Poloidal plane angle between inner leg and plate (rad)",
"(betai)",
self.data.divertor.betai,
),
(
"Poloidal plane angle between outer leg and plate (rad)",
"(betao)",
self.data.divertor.betao,
),
(
"Inner divertor leg poloidal length (m)",
"(plsepi)",
self.data.build.plsepi,
),
(
"Outer divertor leg poloidal length (m)",
"(plsepo)",
self.data.build.plsepo,
),
("Inner divertor plate length (m)", "(plleni)", self.data.build.plleni),
("Outer divertor plate length (m)", "(plleno)", self.data.build.plleno),
("Inner strike point, radial (m)", "(rspi)", spi.r),
("Inner strike point, vertical (m)", "(zspi)", spi.z),
("Inner plate top, radial (m)", "(rplti)", plti.r),
("Inner plate top, vertical (m)", "(zplti)", plti.z),
("Inner plate bottom, radial (m)", "(rplbi)", plbi.r),
("Inner plate bottom, vertical (m)", "(zplbi)", plbi.z),
("Outer strike point, radial (m)", "(rspo)", self.data.build.rspo),
("Outer strike point, vertical (m)", "(zspo)", zspo),
("Outer plate top, radial (m)", "(rplto)", plto.r),
("Outer plate top, vertical (m)", "(zplto)", plto.z),
("Outer plate bottom, radial (m)", "(rplbo)", plbo.r),
("Outer plate bottom, vertical (m)", "(zplbo)", plbo.z),
("Calculated maximum divertor height (m)", "(divht)", divht),
]:
po.ovarre(self.outfile, desc, name, var, "OP ")
elif self.data.divertor.n_divertors == 2:
po.ocmmnt(self.outfile, "Divertor Configuration = Double Null Divertor")
po.oblnkl(self.outfile)
# Assume upper and lower divertors geometries are symmetric.
for desc, name, var in [
(
"Plasma top position, radial (m)",
"(ptop_radial)",
self.data.physics.rmajor - triu * self.data.physics.rminor,
),
(
"Plasma top position, vertical (m)",
"(ptop_vertical)",
self.data.physics.kappa * self.data.physics.rminor,
),
(
"Plasma geometric centre, radial (m)",
"(rmajor)",
self.data.physics.rmajor,
),
("Plasma geometric centre, vertical (m)", "(0.0)", 0.0e0),
("Plasma lower triangularity", "(tril)", tril),
("Plasma elongation", "(kappa)", self.data.physics.kappa),
(
"TF coil vertical offset (m)",
"(dz_tf_plasma_centre_offset)",
self.data.build.dz_tf_plasma_centre_offset,
),
("Plasma upper X-pt, radial (m)", "(rxpt)", xpt.r),
("Plasma upper X-pt, vertical (m)", "(-zxpt)", -xpt.z),
("Plasma outer arc radius of curvature (m)", "(rco)", rco),
("Plasma inner arc radius of curvature (m)", "(rci)", rci),
("Plasma lower X-pt, radial (m)", "(rxpt)", xpt.r),
("Plasma lower X-pt, vertical (m)", "(zxpt)", xpt.z),
(
"Poloidal plane angle between vertical and inner leg (rad)",
"(thetai)",
thetai,
),
(
"Poloidal plane angle between vertical and outer leg (rad)",
"(thetao)",
thetao,
),
(
"Poloidal plane angle between inner leg and plate (rad)",
"(betai)",
self.data.divertor.betai,
),
(
"Poloidal plane angle between outer leg and plate (rad)",
"(betao)",
self.data.divertor.betao,
),
(
"Inner divertor leg poloidal length (m)",
"(plsepi)",
self.data.build.plsepi,
),
(
"Outer divertor leg poloidal length (m)",
"(plsepo)",
self.data.build.plsepo,
),
("Inner divertor plate length (m)", "(plleni)", self.data.build.plleni),
("Outer divertor plate length (m)", "(plleno)", self.data.build.plleno),
("Upper inner strike point, radial (m)", "(rspi)", spi.r),
("Upper inner strike point, vertical (m)", "(-zspi)", -spi.z),
("Upper inner plate top, radial (m)", "(rplti)", plti.r),
("Upper inner plate top, vertical (m)", "(-zplti)", -plti.z),
("Upper inner plate bottom, radial (m)", "(rplbi)", plbi.r),
("Upper inner plate bottom, vertical (m)", "(-zplbi)", -plbi.z),
(
"Upper outer strike point, radial (m)",
"(rspo)",
self.data.build.rspo,
),
("Upper outer strike point, vertical (m)", "(-zspo)", -zspo),
("Upper outer plate top, radial (m)", "(rplto)", plto.r),
("Upper outer plate top, vertical (m)", "(-zplto)", -plto.z),
("Upper outer plate bottom, radial (m)", "(rplbo)", plbo.r),
("Upper outer plate bottom, vertical (m)", "(-zplbo)", -plbo.z),
("Lower inner strike point, radial (m)", "(rspi)", spi.r),
("Lower inner strike point, vertical (m)", "(zspi)", spi.z),
("Lower inner plate top, radial (m)", "(rplti)", plti.r),
("Lower inner plate top, vertical (m)", "(zplti)", plti.z),
("Lower inner plate bottom, radial (m)", "(rplbi)", plbi.r),
("Lower inner plate bottom, vertical (m)", "(zplbi)", plbi.z),
(
"Lower outer strike point, radial (m)",
"(rspo)",
self.data.build.rspo,
),
("Lower outer strike point, vertical (m)", "(zspo)", zspo),
("Lower outer plate top, radial (m)", "(rplto)", plto.r),
("Lower outer plate top, vertical (m)", "(zplto)", plto.z),
("Lower outer plate bottom, radial (m)", "(rplbo)", plbo.r),
("Lower outer plate bottom, vertical (m)", "(zplbo)", plbo.z),
("Calculated maximum divertor height (m)", "(divht)", divht),
]:
po.ovarre(self.outfile, desc, name, var, "OP ")
po.ovarre(
self.outfile,
"Divertor poloidal angle subtended by plasma (degrees)",
"(deg_div_poloidal_plasma)",
self.data.divertor.deg_div_poloidal_plasma,
"OP ",
)
@staticmethod
def plasma_outboard_edge_toroidal_ripple(
ripple_b_tf_plasma_edge_max: float,
r_tf_outboard_mid: float,
n_tf_coils: int,
rmajor: float,
rminor: float,
r_tf_wp_inboard_inner,
r_tf_wp_inboard_centre: float,
r_tf_wp_inboard_outer: float,
dx_tf_wp_primary_toroidal: float,
i_tf_shape: int,
i_tf_sup: int,
dx_tf_wp_insulation: float,
dx_tf_wp_insertion_gap: float,
i_tf_wp_geom: int,
) -> tuple[float, float, int]:
"""Plasma outboard toroidal field (TF) ripple calculation.
This routine computes the TF ripple amplitude at the midplane outboard
plasma edge and the minimum radius of the TF coil centre that would
produce a specified maximum allowed ripple. The calculation uses
fitted coefficients derived from numerical modelling (MAGINT) and
includes a simplified analytical picture-frame coil model for
i_tf_shape == 2.
Parameters
----------
ripple_b_tf_plasma_edge_max : float
Maximum allowed ripple at plasma edge (percent)
r_tf_outboard_mid : float
Radius to the centre of the outboard TF coil leg (m)
n_tf_coils : int
Number of TF coils
rmajor : float
Plasma major radius (m)
rminor : float
Plasma minor radius (m)
r_tf_wp_inboard_inner : float
Inner winding-pack inboard radius (m)
r_tf_wp_inboard_centre : float
Centre winding-pack inboard radius (m)
r_tf_wp_inboard_outer : float
Outer winding-pack inboard radius (m)
dx_tf_wp_primary_toroidal : float
Primary toroidal winding-pack thickness (m)
i_tf_shape : int
TF coil shape switch (2 => picture-frame analytical model)
i_tf_sup : int
TF coil support flag (1 => superconducting)
dx_tf_wp_insulation : float
Winding-pack insulation thickness (m)
dx_tf_wp_insertion_gap : float
Winding-pack insertion gap (m)
i_tf_wp_geom: int
Specifies type of shape for the winding pack
See SuperconductingTFWPShapeType for more information
Returns
-------
tuple[float, float, int]
Tuple containing:
- ripple: Calculated ripple at plasma edge (percent)
- r_tf_outboard_midmin: Minimum r_tf_outboard_mid that yields the specified
maximum ripple (m)
- flag: Applicability flag (0 = OK, non-zero = fitted-range concern)
Raises
------
ProcessValueError
If the TF coil parameters are not properly defined.
Notes
-----
- Fitted coefficients originate from parametric MAGINT runs (M. Kovari, 2014).
- Picture-frame coil analytical model (Ken McClements, 2022) is used when
`i_tf_shape == 2` and gives approximate results (within ~10% of numerical).
- The routine sets an applicability flag when fitted-range assumptions are
exceeded.
"""
match TFConductorModel(i_tf_sup):
case TFConductorModel.SUPERCONDUCTING:
# Minimal inboard WP radius [m]
r_wp_min = r_tf_wp_inboard_inner
match SuperconductingTFWPShapeType(i_tf_wp_geom):
case SuperconductingTFWPShapeType.RECTANGULAR:
r_wp_max = r_wp_min
case SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR:
r_wp_max = r_tf_wp_inboard_centre
case SuperconductingTFWPShapeType.TRAPEZOIDAL:
r_wp_max = r_tf_wp_inboard_outer
# Calculated maximum toroidal WP toroidal thickness [m]
dx_tf_wp_conductor_max = dx_tf_wp_primary_toroidal - 2.0 * (
dx_tf_wp_insulation + dx_tf_wp_insertion_gap
)
# Resistive magnet case
case (
TFConductorModel.WATER_COOLED_COPPER
| TFConductorModel.HELIUM_COOLED_ALUMINIUM
):
# Radius used to define the dx_tf_wp_conductor_max [m]
r_wp_max = r_tf_wp_inboard_outer
# Calculated maximum toroidal WP toroidal thickness [m]
dx_tf_wp_conductor_max = 2.0e0 * r_wp_max * np.tan(np.pi / n_tf_coils)
case _:
raise ProcessValueError("Unsupported TF conductor model.")
flag = 0
if i_tf_shape == TFCoilShapeModel.PICTURE_FRAME:
# Ken McClements ST picture frame coil analytical ripple calc
# Calculated ripple for coil at r_tf_outboard_mid (%)
ripple = 100.0e0 * ((rmajor + rminor) / r_tf_outboard_mid) ** (n_tf_coils)
# Calculated r_tf_outboard_mid to produce a ripple of amplitude
# ripple_b_tf_plasma_edge_max
r_tf_outboard_midmin = (rmajor + rminor) / (
(0.01e0 * ripple_b_tf_plasma_edge_max) ** (1.0e0 / n_tf_coils)
)
else:
# Winding pack to iter-coil at plasma centre toroidal lenth ratio
x = dx_tf_wp_conductor_max * n_tf_coils / rmajor
# Fitting parameters
c1 = 0.875e0 - 0.0557e0 * x
c2 = 1.617e0 + 0.0832e0 * x
# Calculated ripple for coil at r_tf_outboard_mid (%)
ripple = (
100.0e0
* c1
* ((rmajor + rminor) / r_tf_outboard_mid) ** (n_tf_coils - c2)
)
# Calculated r_tf_outboard_mid to produce a ripple of amplitude
# ripple_b_tf_plasma_edge_max
base = 0.01 * ripple_b_tf_plasma_edge_max / c1
# Avoid potential negative or complex result: kludge base to be
# small and positive if required
if base <= 1e-6:
logger.error("base is <= 1e-6. Kludging to 1e-6.")
base = 1e-6
r_tf_outboard_midmin = (rmajor + rminor) / (
base ** (1.0 / (n_tf_coils - c2))
)
if np.isinf(r_tf_outboard_midmin):
logger.error(
"r_tf_outboard_midmin is inf. Kludging to a large value instead."
)
r_tf_outboard_midmin = (rmajor + rminor) * 3
# Notify via flag if a range of applicability is violated
flag = 0
if (x < 0.737e0) or (x > 2.95e0):
flag = 1