4IDH: Magnet 9-1-1 Usage#

4IDH is the magnet hutch, centered around the 9T-1T-1T superconducting vector magnet (magnet911). Typical experiments include XMCD (X-ray Magnetic Circular Dichroism), field-dependent absorption spectroscopy, and field-sweep measurements, all using the circular dichroism data stream.


Starting the Session#

Use the beamline startup script from a terminal:

bluesky-4idh

This activates the polar-bits conda environment and launches IPython with all 4IDH devices pre-loaded (equivalent to from id4_h.startup import *).


Motor Shortcuts#

After the main startup, users run a per-session file (e.g. motor_shortcuts.py) that assigns convenient short names to the magnet’s internal motors:

# motor_shortcuts.py
from apsbits.core.instrument_init import oregistry

_mag = oregistry.find("magnet911")

tabx  = _mag.tab.x       # sample table X
taby  = _mag.tab.y       # sample table Y
tabth = _mag.tab.srot    # sample table rotation
samy  = _mag.samp.y      # sample Y (independent of table)
samth = _mag.samp.th     # sample rotation
field = _mag.ps.field    # magnetic field in Tesla

Run it at the start of each session:

%run motor_shortcuts.py
# or, if it uses wildcard exports:
from motor_shortcuts import *

Beam Alignment#

Find the sample in the beam using the table motors. Use relative scans centered on the current position:

# Scan table X and Y to find peak transmission (I / I0)
RE(lup(tabx, -1, 1, 50, 0.2))
RE(lup(taby, -1, 1, 50, 0.2))

# Fine-tune
RE(lup(tabx, -0.5, 0.5, 30, 0.2))
RE(lup(taby, -0.5, 0.5, 30, 0.2))

# 2D map to locate the sample
RE(grid_scan(
    tabx, -3, 2, 50,
    taby, -2, 2, 40,
    0.2,
))

# Center on peak using the peak finder
RE(lup(tabx, -0.5, 0.5, 30, 0.2))
RE(cen(tabx))   # moves tabx to center-of-mass of the last scan
RE(lup(taby, -0.5, 0.5, 30, 0.2))
RE(cen(taby))

Energy and Phase Retarder Setup#

Set the photon energy (moves the monochromator):

%mov energy 7.245    # Gd L3 edge (~7.245 keV)
%mov energy 7.514    # Tb L3 edge (~7.514 keV)
%mov energy 8.255    # Tb L2 edge (~8.255 keV)

Configure which devices track the energy (call once per session):

energy.tracking_setup(["undulators_ds", "pr2"])

Set the undulator offset relative to the mono energy:

undulators = oregistry.find("undulators")
undulators.ds.energy_offset.put(-0.063)
undulators.ds.energy_deadband.put(0.002)

Phase Retarder (PR2) Setup#

The pr_setup object controls the PR2 piezo and helicity switching:

from id4_common.utils.pr_setup import pr_setup

pr_setup.positioner   = oregistry.find("pr2_pzt_localdc")
pr_setup.offset       = oregistry.find("pr2_pzt_offset_microns")
pr_setup.oscillate_pzt = True

Align PR2 theta:

RE(lup(pr2.th, -0.02, 0.02, 100, 0.2))
%mov pr2.th 22.302          # move to Bragg reflection

Dichroism Data Stream#

The dichro callback accumulates intensity at alternating helicities. It is loaded automatically at startup:

dichro              # DichroStream callback instance
dichro_bec          # BestEffortCallback for the dichro stream
plot_dichro_settings()   # configure dichro plots

Scans with dichro=True read the scaler at each helicity state automatically.


Field Operations#

The field shortcut (from motor_shortcuts.py) exposes the magnet field as a pseudo-motor in Tesla:

field.get()       # read current field value

# Move to a fixed field (use %mov for blocking interactive moves)
%mov field 3.0    # apply +3 T
%mov field 0      # zero field
%mov field -3.0   # apply -3 T (reverse polarity)

Field-Dependent Scans#

Scan the magnetic field while recording XAS intensity with helicity switching:

# Field sweep with dichroism (50 points, 2.5 s dwell per point)
RE(ascan(field, -0.5, 0.5, 50, 2.5, dichro=True))

# Reverse direction scan
RE(ascan(field, 0.5, -0.5, 50, 2.5, dichro=True))

Q-Space Scans with Dichroism#

The qxscan plan scans across an absorption edge in reciprocal space. Parameters are loaded from a JSON file:

qxscan_setup = oregistry.find("qxscan_setup")
qxscan_setup.load_params_json("tb.json")   # load scan range / energy list

# Run a Q-space scan at Tb L3 edge, 1.0 s dwell, with helicity switching
RE(qxscan(7.514, 1.0, dichro=True))

# Without dichroism
RE(qxscan(7.514, 0.5))

Detector Selection#

Select which scaler channels to plot and use as the monitor (see General Examples → Detector Selection):

counters.plotselect(9, 8)     # detector ch 9, monitor ch 8
counters.plotselect([6, 9], 8)   # two detectors, same monitor

In scripts this is typically called once at the top:

counters.plotselect(9, 8)

Reading XAS/XMCD Data#

The functions below are from polartools and are available in the session namespace after startup.

Load a single XAS scan and plot it:

import matplotlib.pyplot as plt

en, xas = load_absorption(
    -1, cat,
    positioner="energy",
    detector="4idhI1",
    monitor="4idhI0",
)
plt.figure()
plt.plot(en, xas)
plt.xlabel("Energy (keV)")
plt.ylabel("XAS (I/I₀)")

Load XMCD from a single dichro scan (returns positioner, XAS, and XMCD arrays):

en, xas, xmcd = load_dichro(
    -1, cat,
    positioner="energy",
    detector="4idhI1",
    monitor="4idhI0",
)

Process and plot XMCD from scans collected at opposing magnetic fields. scans_plus and scans_minus are lists of scan IDs at +field and −field:

plus, minus = process_xmcd(
    scans_plus=[10, 12, 14],
    scans_minus=[11, 13, 15],
    source=cat,
)
plot_xmcd(plus, minus)

Average XAS over multiple scans:

en, xas = load_multi_xas(
    [10, 11, 12], cat,
    positioner="energy",
    detector="4idhI1",
    monitor="4idhI0",
)

RunEngine Control#

RE(count(5, 1.0))   # 5 counts of 1 s each

RE.abort()          # abort current plan
RE.stop()           # graceful stop (runs cleanup)
RE.pause()          # pause (resume with RE.resume())
RE.resume()

Session Metadata#

Tag experiments for later retrieval:

RE.md["proposal_id"]     = "GUP-12345"
RE.md["sample"]          = "TbFe thin film / MgO substrate"
RE.md["experiment_name"] = "XMCD commissioning Jan 2026"

Saving Data#

# Start a new SPEC file for this session
newSpecFile("TbFe_100K_XMCD")
# → creates e.g. 0110_TbFe_100K_XMCD.dat

# Add free-form logbook comments
spec_comment("field +3 T → -3 T sweep, T = 100 K, dichro=True")
spec_comment("PR2 aligned at 22.302 deg, offset = 25.5 V")