Skip to content
Projects

Beamline

Download notebookView on GitHubRun when the site was built.

The pieces of beamtracer.beamline in a row, from the grid to the port: a group of aimed beamlets, the charge states the neutralizer leaves, the residual-ion dump, the duct with its gas and the stray field, and where the power goes. At the end the neutrals at the port are written out as a dataset, in the tokamak’s frame, for the deposition in the plasma (freeofcharge).

The numbers are illustrative: a group of 5 by 16 beamlets of 1 MeV deuterium, 5 mrad each, curved to 7 m; the cross sections are placeholders, as plasmacoll holds no beam stripping data.

import matplotlib.pyplot as plt
import numpy as np
import plasma_plots as pp
from plasmacoll import ChargeStateTransitions
from scipy import constants
from simgeometry import Cuboid
from beamtracer.beamline import (
ApertureGrid,
Duct,
FieldRegion,
Markers,
beamline_summary,
box_field,
gaussian_beam,
profile_field,
)
mass = 2.014 * constants.atomic_mass
speed = np.sqrt(2 * 1e6 * constants.e / mass)
beamlet = gaussian_beam(1500, power=2e5, speed=speed, width=2e-3, divergence=5e-3)
grid = ApertureGrid.regular(16, 5, (0.020, 0.022), focal_length=(7.0, 7.0))
beam = grid.markers(beamlet)
# the neutralizer: 60 % neutrals, 20 % each of D- and D+
beam.state = np.random.default_rng(0).choice(3, size=len(beam), p=[0.2, 0.6, 0.2])
print(f"{len(beam)} markers, {beam.total_power / 1e6:.0f} MW")
120000 markers, 16 MW

The dump: two plates 0.12 m apart from 0.5 to 2.3 m, 20 kV between them, across the narrow side of the group. The duct: a rectangular channel of four plates from 2.3 to 6 m, its bore 0.12 by 0.24 m, with 1e18 m⁻³ of D2_2 and a vertical stray field rising from 5 to 50 mT towards the port at 6 m.

dump_start, dump_end, gap = 0.5, 2.3, 0.12
dump_length = dump_end - dump_start
plates = [
Cuboid(
((dump_start + dump_end) / 2, s * (gap / 2 + 0.005), 0),
(dump_length, 0.01, 0.5),
)
for s in (1, -1)
]
dump = FieldRegion(
plates,
exit=dump_end,
mass=mass,
electric=box_field(
(0, 20e3 / gap, 0), (dump_start, -gap / 2, -0.25), (dump_end, gap / 2, 0.25)
),
step=2e-3,
).trace(beam)
port, half_y, half_z, wall = 6.0, 0.06, 0.12, 0.05
middle, duct_length = (dump_end + port) / 2, port - dump_end
duct_walls = [
Cuboid(
(middle, s * (half_y + wall / 2), 0), (duct_length, wall, 2 * (half_z + wall))
)
for s in (1, -1)
] + [
Cuboid((middle, 0, s * (half_z + wall / 2)), (duct_length, 2 * half_y, wall))
for s in (1, -1)
]
transitions = ChargeStateTransitions({"D2": {("0", "+"): 1.5e-20}})
duct = Duct(duct_walls, port=port, transitions=transitions, gas={"D2": 1e18}).trace(
dump.exit
)
stray = profile_field([dump_end, port], [[0, 0], [0, 0], [0.005, 0.05]])
bent = FieldRegion(duct_walls, exit=port, mass=mass, magnetic=stray).trace(
duct.reionized
)
summary = beamline_summary(
Markers.concatenate([duct.port, bent.exit]),
{"dump": dump.wall, "duct": duct.wall, "duct, reionized": bent.wall},
power=beam.total_power,
work=dump.work + bent.work, # what the dump field gave the ions
halo_radius=0.05,
footprint_extent=(-0.1, 0.1, -0.15, 0.15),
bins=50,
)
summary
<xarray.Dataset> Size: 21kB
Dimensions:       (part: 3, y: 50, z: 50)
Coordinates:
  * part          (part) <U15 180B 'dump' 'duct' 'duct, reionized'
  * y             (y) float64 400B -0.098 -0.094 -0.09 ... 0.09 0.094 0.098
  * z             (z) float64 400B -0.147 -0.141 -0.135 ... 0.135 0.141 0.147
Data variables:
    power_in      float64 8B 1.6e+07
    power_work    float64 8B 6.386e+04
    power_port    float64 8B 8.488e+06
    power_on      (part) float64 24B 6.461e+06 7.123e+05 4.021e+05
    transmission  float64 8B 0.5305
    power_halo    float64 8B 1.958e+06
    footprint     (y, z) float64 20kB 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0
parts = ["port"] + list(summary.part.values)
values = [float(summary.power_port)] + list(summary.power_on.values)
with pp.figure(figsize=(7, 3.2)) as fig:
ax = fig[0]
ax.barh(parts[::-1], np.array(values[::-1]) / 1e6, color="#2a78d6")
for k, v in enumerate(values[::-1]):
ax.text(
v / 1e6,
k,
f" {v / 1e6:.1f} MW ({v / float(summary.power_in):.0%})",
va="center",
)
ax.set_xlabel("power [MW]")
ax.set_xlim(0, 1.3 * max(values) / 1e6)
Notebook output

The dump takes the 40 % of the beam that left the neutralizer charged. Of the neutrals the duct walls take the tails of the beam, the gas reionizes a few percent, and the stray field turns those onto the walls near the port. The rest, about half the beam, reaches the port.

The group converges towards its focus at 7 m, 1 m behind the port. The power density at the port, clipped at y=±0.06y = \pm 0.06 m by the duct, with a circle of 5 cm round the beam’s centre; the power beyond it is power_halo. Here that is the Gaussian tail of the beamlets: the halo the experiments see, a broad component of some 30 mrad, appears only if the beamlet carries one.

footprint = (summary.footprint / 1e6).assign_attrs(units="MW/m²")
result = footprint.plasma.plot.slice(
x="y", y="z", equal_aspect=True, colorbar_label="MW/m²"
)
ax = plt.gca()
ax.add_patch(plt.Circle((0, 0), 0.05, fill=False, color="white", ls="--"))
print(
f"beyond 5 cm: {float(summary.power_halo) / float(summary.power_port):.1%} of the port power"
)
beyond 5 cm: 23.1% of the port power
Notebook output

The neutrals through the port, placed in the tokamak’s frame (here the port at R=9R = 9 m, the beam aimed at a tangency radius of 5.3 m, horizontal), as a dataset with their weight in atoms per second, which freeofcharge’s NeutralBeam.from_dataset reads:

at_port = Markers.concatenate([duct.port, bent.exit]).drift_to(port)
neutral = at_port.subset(at_port.state == at_port.states.index("0"))
tangency, start = 5.3, 9.0
along = np.sqrt(start**2 - tangency**2)
origin = (tangency, -along, 0.0)
direction = (0.0, 1.0, 0.0)
markers = neutral.placed(origin, direction, x=port).to_dataset(mass)
markers
<xarray.Dataset> Size: 4MB
Dimensions:    (component: 3, marker: 62950)
Coordinates:
  * component  (component) <U1 12B 'x' 'y' 'z'
Dimensions without coordinates: marker
Data variables:
    position   (component, marker) float64 2MB 5.317 5.246 ... 0.02505 0.01953
    velocity   (component, marker) float64 2MB -3.69e+04 ... -2.352e+05
    power      (marker) float64 504kB 133.3 133.3 133.3 ... 133.3 133.3 133.3
    weight     (marker) float64 504kB 8.322e+14 8.322e+14 ... 8.322e+14
    state      (marker) <U1 252kB '0' '0' '0' '0' '0' ... '0' '0' '0' '0' '0'
Attributes:
    mass:     3.3443256848048796e-27
    states:   ['-', '0', '+']