Evaluating expressions¶
A PALS lattice may write numeric values as mathematical expressions —
0.3 * r_electron, a_const^2, mass_of("proton"). When
parse_and_expand_pals expands a lattice it evaluates every such expression to
a plain number — across the expanded views (expanded and
full_expanded, which hold the same values) and adjunct — so the
expanded lattice is fully numeric and ready for a simulation program to consume.
The original and combined views always keep the expression text exactly as
written.
import palsparserpy as pp
lat = pp.parse_and_expand_pals("ex.pals.yaml")
# `combined` keeps the source text; the evaluated number is downstream of it.
pp.to_yaml_string(lat.combined) # length: 0.3 * r_electron
pp.to_yaml_string(lat.expanded) # length: 8.4538209614999992e-16
Only scalars that are genuine expressions are touched. Names that happen to sit
where a value could go — element and line references in a line:, kind:
names, booleans — are not expressions and are left untouched.
What gets evaluated, and when¶
Two kinds of expression are evaluated to a number in the expanded tree:
Immediate expressions — a bare value such as
length: 0.3 * r_electron.Delayed expressions wrapped in
expr(...), such asKn1: expr(3.74 * a_var). In the fully expanded tree the distinction no longer matters: both become numbers.
One case is deliberately not evaluated. An expression that calls random()
or random_gauss() is left as text, so that expanding the same lattice twice
gives byte-identical output:
Kn2: 0.01 + 0.003*random_gauss() # kept verbatim in `expanded`
Grammar¶
The expression grammar is the arithmetic you would expect:
operators
+-*/^, with the usual precedence;^(power) is right-associative, so2^3^2is2^(3^2) = 512;a unary sign binds looser than
^, so-2^2is-(2^2) = -4, while a signed exponent still works:2^-2is2^(-2) = 0.25— the Fortran/Bmad convention used across the ecosystem;parentheses group sub-expressions.
Note that this is PALS’ grammar, not Python’s: ^ is exponentiation here, where
Python spells it **.
Built-in constants¶
The named physical constants below are available in every expression. Their values come from AtomicAndPhysicalConstantsCLib (a C++ mirror of AtomicAndPhysicalConstants.jl, CODATA 2022), so PALS shares one set of numbers with the rest of the toolchain.
Constant |
Meaning |
|---|---|
|
π |
|
speed of light |
|
Planck constant |
|
reduced Planck constant |
|
Boltzmann constant |
|
classical electron / proton radius |
|
elementary charge |
|
vacuum permeability / permittivity |
|
1 / (4π ε₀ c²) |
|
fine-structure constant |
|
Avogadro constant |
Functions¶
Standard math functions are available: sqrt, exp, log, abs, sign,
factorial; the trigonometric and hyperbolic families and their inverses
(sin, cos, tan, cot, sinc, asin, acos, atan, atan2, sinh,
cosh, tanh, coth, asinh, acosh, atanh, acoth); and the rounding
helpers int (toward zero), nint (nearest), floor, ceiling, and
modulo(x, p).
Particle-data functions¶
mass_of, charge_of, and anomalous_moment_of look a particle up by name and
return its mass (eV), charge (units of e), or anomalous magnetic moment. The
species name must be quoted (single or double quotes); an unquoted name is an
error. A mass number must carry a leading # — write "#3He", not "3He".
Quoting also lets that # be written without tripping YAML’s comment rule:
m_e: mass_of("electron")
q_he: charge_of("helion")
b_const: 0.45 * mass_of("#3He")
The argument may also be a user constant or variable whose value is a species name, passed by name (without quotes), so one definition can fix the species and every expression refer to it:
- constants:
species: "#3He" # a species-valued constant
b_const: 0.45 * mass_of(species) # resolves species -> "#3He"
Such a species-valued constant may also be referenced directly wherever a species name is expected: a bare identifier used as a parameter value is replaced by the constant’s species string in the expanded tree.
- begin:
kind: BeginningEle
ReferenceP:
species_ref: species # -> "#3He" in `expanded`
User constants and variables¶
A lattice can define its own constants and variables and refer to them by name from later expressions. Both the full form and the compact form are recognised:
facility:
# Full form.
- r_scaled:
kind: constant
value: 0.3 * r_electron
# Compact form (a seq of single-key maps, or a plain map — both accepted).
- constants:
a_const: 0.3 * r_electron
b_const: 0.45
- variables:
a_var: a_const^2 # may reference an earlier definition
Definitions are resolved in dependency order, so a later value may reference an
earlier one (a_var uses a_const above). A reference that cannot be resolved,
or a genuine cycle, leaves the value as text rather than raising.
Element-parameter references¶
An expression may also reference another element’s parameter by name, using the
element>group.sub. … .param syntax (the same parameter path used elsewhere in
the standard). It resolves to that parameter’s value, itself evaluated as an
expression:
- thingB:
kind: Sextupole
MagneticMultipoleP:
Kn2L: 0.1
- DH1A:
kind: Bend
BendP:
edge_int2: 0.02 * thingB>MagneticMultipoleP.Kn2L # → 0.002
The reference names one specific element (an exact name — pattern matching is not used in a value expression) and its full parameter path. As with any other reference, one that cannot be resolved leaves the value as text.
Controllers¶
A Controller element bundles expressions that drive lattice parameters. Its
variables: form a symbol table scoped to that controller, and each
controls: entry pairs a parameter target with an expression. During
expansion the controller variables are evaluated against that scoped table, and
each control expression is computed and written back into its control entry.
Controller variables may reference one another and, via the
controller>variable syntax, variables of another controller:
- ps27:
kind: Controller
control_type: ABSOLUTE
variables:
cur1: 0.023
cur2: cur1 / c_light # references an earlier controller variable
controls:
- parameter: Qa.*>MagneticMultipoleP.Ks2L
expression: 0.075*sin(cur1) + 0.3*cur2 # → a number in `expanded`
The parameter target specification and control_type are names, not
expressions, and are left untouched.
Evaluating a single expression¶
evaluate_pals_expression evaluates one expression string on its own and
returns a float. It is handy for quick checks and for reusing the same grammar
outside a lattice:
pp.evaluate_pals_expression("3.75e7 / c_light^2") # 4.172…e-10
pp.evaluate_pals_expression('mass_of("electron")') # 510998.95069…
pp.evaluate_pals_expression("expr(2 * pi)") # 6.283…
This evaluates a standalone string, so user-defined constants and variables
are not in scope — use parse_and_expand_pals for whole-lattice
evaluation, whose expanded trees already have every expression resolved. It
raises ValueError when the string is not evaluable: a parse error, an unknown
identifier or species, an unquoted species name, a random()/random_gauss()
expression (intentionally deferred), or a non-finite result.
A runnable version of these examples is in examples/evaluate_expressions.py.