beamtracer.optimization.einzel
An einzel lens: the first design problem of beamtracer.optimization.
Three coaxial tubes in cylindrical (x, r) geometry, the beam axis along x: the
outer two at 0 V, the middle one at the design voltage V, inside a grounded wall.
Each tube is electrode_length long and thickness thick, its bore at the radius
aperture, with half-round ends (made of a simgeometry rectangle and two circles: a
sharp corner has a singular surface field, which grows as the mesh is refined); gap
separates neighbouring tubes. A parallel beam of ions (protons by default) is launched upstream with energy and
radius beam_radius, and the objective is its rms size at the target plane
downstream. Trajectories that hit an electrode are lost, and the lost fraction of the
current is a constraint, as is the largest field on the electrode surfaces.
EinzelLens holds the fixed settings and makes the
beamtracer.optimization.Design and
beamtracer.optimization.Problem of any of the parameters voltage,
gap and aperture; EinzelLens.thin_lens_voltage and
EinzelLens.paraxial_voltage are the voltages that focus at the target in the
thin-lens and paraxial approximations.
The thin lens
For a paraxial ray in an axially symmetric electrostatic field, with the potential relative to the ions’ zero of kinetic energy (the kinetic energy per charge, on the axis for ions starting at ), the ray equation (non-relativistic)
becomes, with the Picht transformation ,
An einzel lens has the same potential on both sides, so and agree up to a constant outside it; for a weak (thin) lens hardly changes inside it, and a parallel ray of radius leaves with the slope ,
See the paraxial ray equation and electrostatic lenses in M. Reiser, Theory and Design
of Charged Particle Beams (Wiley, 2nd ed. 2008), S. Humphries, Principles of Charged
Particle Acceleration (Wiley, 1986) and H. Wollnik, Optics of Charged Particles
(Academic Press, 1987). It holds for paraxial rays and
a lens short compared with f; must stay positive (no ion is
reflected). Here the thin lens sits at the middle electrode, and its focal length is the
distance to the target. EinzelLens.paraxial_voltage integrates the Picht equation
through the whole lens instead (thick, still paraxial and without aberrations).
Attributes
| Name | Description |
|---|---|
ELECTRODES | No description. |
NEGLIGIBLE_CURRENT | No description. |
PARAMETERS | No description. |
Classes
| Name | Description |
|---|---|
EinzelLens | The fixed settings of an einzel lens, and its problems. |
ELECTRODESattributemodule attribute#
ELECTRODES = ('upstream', 'middle', 'downstream')NEGLIGIBLE_CURRENTattributemodule attribute#
NEGLIGIBLE_CURRENT = 1e-09PARAMETERSattributemodule attribute#
PARAMETERS = {'voltage': 'V', 'gap': 'm', 'aperture': 'm'}EinzelLensclassdataclass#
class EinzelLens(energy: float = unit_field('eV', 30000.0), charge: float = 1.0, mass: float = unit_field('u', 1.007276), current: float = unit_field('A', 0.0), beam_radius: float = unit_field('m', 0.002), num_particles: int = 60, start: float = unit_field('m', 0.001), voltage: float = unit_field('V', 15000.0), gap: float = unit_field('m', 0.004), aperture: float = unit_field('m', 0.005), electrode_length: float = unit_field('m', 0.008), thickness: float = unit_field('m', 0.002), rounded: bool = True, centre: float = unit_field('m', 0.04), target: float = unit_field('m', 0.12), length: float = unit_field('m', 0.13), radius: float = unit_field('m', 0.02), cell: float = unit_field('m', 0.0005), field_limit: float = unit_field('V/m', 5000000.0), max_loss: float = 0.0, iterations: int = 40, tolerance: float = unit_field('V', 0.01), relaxation: float = 0.8, save_trajectories: int = 0)The fixed settings of an einzel lens, and its problems.
The beam: ions of energy [eV], charge [e] and mass [u] (protons by
default), current [A] (0: no space charge), launched parallel at
x = start [m] from the axis to beam_radius [m] as num_particles rings.
The lens: three tubes of electrode_length [m] and thickness [m], the middle
one centred on x = centre [m], with half-round ends if rounded;
voltage [V], gap [m] and aperture [m] are the values of the parameters
not being optimized. The domain is length by radius [m] with nodes every
cell [m], grounded at both ends and on the outer wall; the objective is the rms size (sqrt(<r^2> / 2), the projected rms) at
x = target [m]. The constraints are the largest surface field over
field_limit [V/m], minus 1, and the lost fraction of the current minus
max_loss. iterations, tolerance [V] and relaxation set the Vlasov
iteration with space charge; without it, one field solve and trace is the solution.
Every physical setting takes an astropy Quantity or a string too ("30 keV",
4 * u.mm), converted to these units.
A frozen dataclass, so it pickles: its config and evaluate are the
build and evaluation functions of its problems, also in a process pool.
Numerics
ray
objective
constraints
transmission
Assumptions:
- three coaxial tubes, the outer two grounded, the middle one at , inside a grounded wall; a parallel beam launched upstream as rings
- without space charge (
current0) one field solve and trace is the solution, with A as the weight of the moments - a design that does not fit is a failed evaluation
References:
- Reiser, Theory and Design of Charged Particle Beams, 2nd ed. (2008)
- Humphries, Principles of Charged Particle Acceleration (1986)
- Wollnik, Optics of Charged Particles (1987)
Built from:
beamtracer.model.BeamOpticsbeamtracer.optimization.einzel.EinzelLens.thin_lens_focal_lengthbeamtracer.optimization.einzel.EinzelLens.paraxial_focus
apertureattributeclass attributeinstance attribute#
aperture: float = unit_field('m', 0.005)beam_radiusattributeclass attributeinstance attribute#
beam_radius: float = unit_field('m', 0.002)cellattributeclass attributeinstance attribute#
cell: float = unit_field('m', 0.0005)centreattributeclass attributeinstance attribute#
centre: float = unit_field('m', 0.04)chargeattributeclass attributeinstance attribute#
charge: float = 1.0currentattributeclass attributeinstance attribute#
current: float = unit_field('A', 0.0)electrode_lengthattributeclass attributeinstance attribute#
electrode_length: float = unit_field('m', 0.008)energyattributeclass attributeinstance attribute#
energy: float = unit_field('eV', 30000.0)field_limitattributeclass attributeinstance attribute#
field_limit: float = unit_field('V/m', 5000000.0)gapattributeclass attributeinstance attribute#
gap: float = unit_field('m', 0.004)iterationsattributeclass attributeinstance attribute#
iterations: int = 40lengthattributeclass attributeinstance attribute#
length: float = unit_field('m', 0.13)massattributeclass attributeinstance attribute#
mass: float = unit_field('u', 1.007276)max_lossattributeclass attributeinstance attribute#
max_loss: float = 0.0num_particlesattributeclass attributeinstance attribute#
num_particles: int = 60radiusattributeclass attributeinstance attribute#
radius: float = unit_field('m', 0.02)relaxationattributeclass attributeinstance attribute#
relaxation: float = 0.8roundedattributeclass attributeinstance attribute#
rounded: bool = Truesave_trajectoriesattributeclass attributeinstance attribute#
save_trajectories: int = 0startattributeclass attributeinstance attribute#
start: float = unit_field('m', 0.001)targetattributeclass attributeinstance attribute#
target: float = unit_field('m', 0.12)thicknessattributeclass attributeinstance attribute#
thickness: float = unit_field('m', 0.002)toleranceattributeclass attributeinstance attribute#
tolerance: float = unit_field('V', 0.01)voltageattributeclass attributeinstance attribute#
voltage: float = unit_field('V', 15000.0)axis_potentialmethod#
def axis_potential(values: Mapping[str, float] | None = None) -> tuple[np.ndarray, np.ndarray]Return x [m] and the potential on the axis per volt on the middle tube.
From a field solve at the given geometry (without space charge, the potential is proportional to the voltage).
configmethod#
def config(values: Mapping[str, float] | None = None) -> BeamOpticsConfigReturn the beamtracer.BeamOpticsConfig of the lens.
values are some of voltage, gap and aperture; the others are the
lens defaults. Raises ValueError for a lens that does not fit between
the start of the beam and the target.
designmethod#
def design(bounds: Mapping[str, tuple[float, float]]) -> DesignReturn the design of the parameters in bounds ({name: (lower, upper)}).
electrodemethod#
def electrode(name: str, x: float, aperture: float, voltage: float) -> dict[str, ObjectParams]Return the objects of a tube centred on x with its bore at aperture.
A tube of electrode_length along the axis and thickness across it. With
rounded its ends are half circles (a rectangle and two circles, named
name, name.upstream and name.downstream); without, it is a rectangle
with sharp corners, whose surface field is singular.
electrode_positionsmethod#
def electrode_positions(gap: float) -> tuple[float, float, float]Return the centres [m] of the three tubes.
evaluatemethod#
def evaluate(config: BeamOpticsConfig) -> EvaluationSolve the lens of a configuration and return its measure.
kinetic_potentialmethod#
def kinetic_potential(potential: np.ndarray) -> np.ndarrayReturn the ions’ kinetic energy per charge, phi [V], at a potential [V].
measuremethod#
def measure(model: BeamOptics) -> EvaluationReturn the beamtracer.optimization.Evaluation of a solved lens.
The objective spot is the rms size [m] at the target; the constraints are
surface_field (the largest surface field over field_limit, minus 1) and
loss (the lost fraction of the current minus max_loss). The diagnostics
hold the field [V/m], the transmission, the rms, 90 % and largest radii [m] at
the target, and the Vlasov iterations.
paraxial_focusmethod#
def paraxial_focus(x: np.ndarray, potential: np.ndarray) -> floatReturn where a paraxial ray, parallel at the start, crosses the axis [m].
The Picht equation integrated through the on-axis potential [V] (a thick
lens, without aberrations); a ray still away from the axis at the end of the
domain is continued straight (the field is zero there). inf if it diverges.
Numerics
picht
focus
Assumptions:
- a thick lens, paraxial and without aberrations; integrated by
solve_ivp(rtol , at most one cell per step), the coefficient interpolated linearly - for a diverging ray; the voltage that focuses on the target by Brent’s method
paraxial_voltagemethod#
def paraxial_voltage(values: Mapping[str, float] | None = None) -> floatReturn the voltage whose paraxial (thick-lens) focus is on the target.
Like thin_lens_voltage, but integrating the paraxial ray through the
lens (paraxial_focus).
problemmethod#
def problem(bounds: Mapping[str, tuple[float, float]], constraints: tuple[str, ...] = ('loss')) -> ProblemReturn the problem of minimizing the spot over the parameters in bounds.
constraints are some of "loss" and "surface_field".
solvemethod#
def solve(values: Mapping[str, float] | None = None) -> BeamOpticsReturn the solved beamtracer.BeamOptics of the lens.
surface_fieldsmethodstaticmethod#
def surface_fields(model: BeamOptics) -> dict[str, float]Return the largest surface field [V/m] of each tube (of all its objects).
thin_lens_focal_lengthmethod#
def thin_lens_focal_length(x: np.ndarray, potential: np.ndarray) -> floatReturn the thin-lens focal length [m] of the on-axis potential [V].
1 / f = (3 / 16) int (phi' / phi)^2 dx (see the module docstring), with
phi the kinetic energy per charge; infinite without a lens, NaN if an ion
is stopped.
Numerics
thin lens
discrete
voltage
Order of accuracy: 2.
Assumptions:
- paraxial rays, a lens short compared with , the same potential on both sides; everywhere (NaN if an ion is stopped), without a lens
- the on-axis potential per volt from one vacuum solve at 1 kV on the middle tube
- the order is in the node spacing
thin_lens_voltagemethod#
def thin_lens_voltage(values: Mapping[str, float] | None = None) -> floatReturn the voltage whose thin lens at the middle tube focuses on the target.
The voltage has the sign of the lens default voltage (for positive ions,
positive decelerates them and negative accelerates them), at the geometry of
values. Raises ValueError if no voltage of that sign focuses there.
valuesmethod#
def values(values: Mapping[str, float] | None = None) -> dict[str, float]Return all three parameters: values, the defaults for the others.