Examples
This section provides practical examples for creating and working with LAURA elements, lattices, and machine models in pure Python. These examples demonstrate the fundamental workflows for building accelerator lattice descriptions programmatically.
Creating Elements
Basic Element Creation
The simplest LAURA element requires only basic identification properties:
from laura.models.element import baseElement
# Create a basic element
element = baseElement(
name="QUAD-01",
hardware_class="Magnet",
hardware_type="Quadrupole",
machine_area="LINAC-1"
)
print(element.name) # "QUAD-01"
print(element.hardware_info) # {"class": "Magnet", "type": "Quadrupole"}
Physical Elements
Elements with physical properties include position and dimensions:
from laura.models.element import PhysicalBaseElement
from laura.models.physical import PhysicalElement, Position
# Create element with physical properties
cavity = PhysicalBaseElement(
name="CAV-01",
hardware_class="RF",
hardware_type="Cavity",
machine_area="LINAC-1",
physical=PhysicalElement(
length=1.0,
middle=Position(x=0, y=0, z=5.0)
)
)
# Access physical properties
print(cavity.physical.length) # 1.0
print(cavity.physical.start.z) # 4.5
print(cavity.physical.end.z) # 5.5
Complete Elements
Full element definitions include electrical, manufacturer, and simulation properties:
from laura.models.element import Element
from laura.models.electrical import ElectricalElement
from laura.models.manufacturer import ManufacturerElement
from laura.models.simulation import SimulationElement
quad = Element(
name="QUAD-02",
hardware_class="Magnet",
hardware_type="Quadrupole",
machine_area="LINAC-1",
electrical=ElectricalElement(
maxI=100.0,
minI=-100.0,
read_tolerance=0.1
),
manufacturer=ManufacturerElement(
manufacturer="Company XYZ",
model="QD-2000",
serial_number="SN12345"
),
simulation=SimulationElement(
field_amplitude=25.0
)
)
# Access nested properties directly
print(quad.maxI) # 100.0 (finds electrical.maxI)
print(quad.serial_number) # "SN12345"
Importing from YAML
Elements can also be created from YAML files:
# INJ-MAG-DIP-01.yaml
alias: INJ:DIP1
controls:
variables:
readback:
description: Readback of INJ-MAG-DIP-01
dtype: float
identifier: INJ-MAG-DIP-01:RBV
protocol: CA
read_only: true
units: N/A
setpoint:
description: Setpoint of INJ-MAG-DIP-01
dtype: float
identifier: INJ-MAG-DIP-01:SP
protocol: PVA
read_only: true
units: N/A
state:
description: State of INJ-MAG-DIP-01
dtype: int
identifier: INJ-MAG-DIP-01:STATE
protocol: CA
read_only: false
units: N/A
degauss:
steps: 11
tolerance: 0.5
values: [115.77, -115.77, 69.6, -69.6, 46.4, -46.4, 23.2, -23.2, 11.6, -11.6, 0.0]
electrical:
maxI: 116
minI: -116.0
read_tolerance: 0.1
hardware_class: Magnet
hardware_model: Generic
hardware_type: Dipole
machine_area: INJ
magnetic:
multipoles:
K0L:
normal: 0.5
order: 0
radius: 0.0
skew: 0.0
order: 0
random_multipoles: {}
skew: false
systematic_multipoles: {}
manufacturer:
hardware_class: Dipole
manufacturer: Dipole Type 1
serial_number: '13256'
name: INJ-MAG-DIP-01
physical:
datum: [-0.14887689, 0.0, 1.18670069]
error:
position: [0.0, 0.0, 0.0]
rotation: [0.0, 0.0, 0.0]
global_rotation: [0.0, 0.0, 0.0]
length: 0.399216
middle: [0.0, 0.0, 1.03782375]
rotation: [0.0, 0.0, 0.0]
survey:
position: [0.0, 0.0, 0.0]
rotation: [0.0, 0.0, 0.0]
subelement: false
virtual_name: V-INJ-MAG-DIP-01
This can then be loaded in as a LAURA object:
from laura.Importers.YAML_Loader import interpret_YAML_Element, read_YAML_Element_File
filename = "INJ-MAG-DIP-01.yaml"
inj_dip_01 = read_YAML_Element_File(filename)
print(inj_dip_01.middle)
Creating Lattice Sections
Sections group elements into ordered sequences:
from laura.models.elementList import SectionLattice, ElementList
from laura.models.element import PhysicalBaseElement
from copy import deepcopy
# Create elements for a section
elem1 = PhysicalBaseElement(
name="BPM-01",
hardware_class="Diagnostic",
hardware_type="BPM",
machine_area="INJECTOR",
physical={"middle": {"x": 0.0, "y": 0.0, "z": 1.0}, "length": 0.1}
)
elem2 = PhysicalBaseElement(
name="QUAD-01",
hardware_class="Magnet",
hardware_type="Quadrupole",
machine_area="INJECTOR",
physical={"middle": {"x": 0.0, "y": 0.0, "z": 2.0}, "length": 0.2}
)
elem3 = PhysicalBaseElement(
name="BPM-02",
hardware_class="Diagnostic",
hardware_type="BPM",
machine_area="INJECTOR",
physical={"middle": {"x": 0.0, "y": 0.0, "z": 3.0}, "length": 0.1}
)
# Create section with ordered elements
section = SectionLattice(
name="INJECTOR",
order=["BPM-01", "QUAD-01", "BPM-02"],
elements=ElementList(elements={
"BPM-01": elem1,
"QUAD-01": elem2,
"BPM-02": elem3
})
)
# Access section properties
print(section.names) # ["BPM-01", "QUAD-01", "BPM-02"]
print(section["QUAD-01"].hardware_type) # "Quadrupole"
print(section[1].name) # "QUAD-01" (by index)
Working with Drifts
Automatically insert drift spaces between elements:
# Create drifts between elements
elements_with_drifts = section.createDrifts()
# The result includes original elements plus drifts
for name, elem in elements_with_drifts.items():
if "drift" in name:
print(f"Drift: {name}, Length: {elem.physical.length}")
S-Position Calculation
Calculate cumulative path length along the beamline:
# Get S-positions as list
s_values = section.get_s_values()
print(s_values) # [0.95, 1.1, 1.9, 2.1, 2.95]
# Get S-positions as dictionary
s_dict = section.get_s_values(as_dict=True)
print(s_dict["QUAD-01"]) # S-position of QUAD-01
# Start from a different S-value
s_values_offset = section.get_s_values(starting_s=10.0)
Creating Machine Layouts
Layouts combine multiple sections into beam paths:
from laura.models.elementList import MachineLayout
# Create another section
linac_section = SectionLattice(
name="LINAC-1",
order=["CAV-01", "BPM-03"],
elements=ElementList(elements={
"CAV-01": PhysicalBaseElement(
name="CAV-01",
hardware_class="RF",
hardware_type="Cavity",
machine_area="LINAC-1",
physical={"middle": {"x": 0.0, "y": 0.0, "z": 10.0}, "length": 1.0}
),
"BPM-03": PhysicalBaseElement(
name="BPM-03",
hardware_class="Diagnostic",
hardware_type="BPM",
machine_area="LINAC-1",
physical={"middle": {"x": 0.0, "y": 0.0, "z": 12.0}, "length": 0.1}
)
})
)
# Create layout from sections
layout = MachineLayout(
name="MainBeamline",
sections={
"INJECTOR": section,
"LINAC-1": linac_section
}
)
# Access layout properties
print(layout.names) # ["INJECTOR", "LINAC-1"]
print(layout.elements) # All element names in order
# Get specific element
cav = layout.get_element("CAV-01")
print(cav.machine_area) # "LINAC-1"
# Filter elements by type
bpms = layout.get_all_elements(element_type="BPM")
print(bpms) # ["BPM-01", "BPM-02", "BPM-03"]
# Get elements between two points
between = layout.elements_between(
start="QUAD-01",
end="CAV-01"
)
print(between) # Elements from QUAD-01 to CAV-01
Creating Machine Models
The complete machine model manages all elements, sections, and layouts:
from laura.models.elementList import MachineModel
from laura.models.element import Element
# Define elements dictionary
elements = {
"MAG-01": Element(
name="MAG-01",
hardware_class="Magnet",
hardware_type="Quadrupole",
machine_area="AREA-01"
),
"BPM-01": Element(
name="BPM-01",
hardware_class="Monitor",
hardware_type="BPM",
machine_area="AREA-01"
),
"CAV-01": Element(
name="CAV-01",
hardware_class="RF",
hardware_type="Cavity",
machine_area="AREA-02"
)
}
Building from Elements Only
LAURA can automatically create sections from machine areas:
# Create model with elements only
# Sections are auto-generated from machine_area
model = MachineModel(elements=elements)
print(list(model.sections.keys())) # ["AREA-01", "AREA-02"]
print(model.sections["AREA-01"].names) # ["MAG-01", "BPM-01"]
Defining Explicit Sections
Specify the order of elements within sections:
model = MachineModel(
elements=elements,
section={
"sections": {
"AREA-01": ["BPM-01", "MAG-01"], # Custom order
"AREA-02": ["CAV-01"]
}
}
)
print(model.sections["AREA-01"].order) # ["BPM-01", "MAG-01"]
Complete Model with Layouts
Define beam paths through the machine:
model = MachineModel(
elements=elements,
section={
"sections": {
"AREA-01": ["MAG-01", "BPM-01"],
"AREA-02": ["CAV-01"]
}
},
layout={
"layouts": {
"main_beam": ["AREA-01", "AREA-02"],
"bypass": ["AREA-01"] # Alternative path
},
"default_layout": "main_beam"
}
)
# Access layouts
print(list(model.lattices.keys())) # ["main_beam", "bypass"]
print(model.default_path) # "main_beam"
# Get elements along specific path
main_elements = model.elements_between(
path="main_beam"
)
print(main_elements) # ["MAG-01", "BPM-01", "CAV-01"]
bypass_elements = model.elements_between(
path="bypass"
)
print(bypass_elements) # ["MAG-01", "BPM-01"]
Dynamic Model Updates
Add elements to an existing model:
# Start with empty model
model = MachineModel()
# Add elements dynamically
new_elements = {
"NEW-01": Element(
name="NEW-01",
hardware_class="Magnet",
hardware_type="Dipole",
machine_area="NEW-AREA"
)
}
model.append(new_elements)
# Sections are automatically updated
print("NEW-AREA" in model.sections) # True
print(model["NEW-01"].hardware_type) # "Dipole"