AI Applications for Medical Accelerators
Accelerator Side
CAS Topical Course on Medical Accelerators · Jurmala, Latvia · June 2026
Dr. Adnan Ghribi — GANIL / CNRS-IN2P3 · adnan.ghribi@ganil.fr
Part 1 — Introduction
Context · Brief history of AI · AI through the physicist lens
Part 2 — AI in accelerators
A 30 years dive · Practical foundations Practical applications · Future directions & opportunities
Summary · References
Three companion notebooks — open on Colab now:
01_fault_detection — autoencoder on RF waveforms02_beam_tuning — Bayesian optimisation with Cheetah03_surrogate_model — MLP surrogate + ensemble UQIn scope — accelerator side
Out of scope today
→ covered in the companion lecture “AI — the medical side”.
model.fit(X_train) anomaly_scores = model.predict(X_test)
aghribi/cas2026-ai-medical-accelerators
▶ Open in Colab 01_fault_detection · 02_beam_tuning · 03_surrogate
A glossary of acronyms is at the end of the deck, before the backup slides.
Part 1
Introduction
Context · Brief history of AI · AI through the physicist lens
★ autoencoders · ★ MLP surrogates · CNN / Transformer · deep RL · generative (VAE · diffusion · LLM)
Demystified today — with live code
Not covered here: symbolic AI · LLMs & agents · generative models — touched only in future directions.
From the first neuron to the Nobel Prize in Physics

Warren McCulloch & Walter Pitts — first mathematical model of a neuron, mapping neural firing onto Boolean logic (1943).
How a neuron works:


McCulloch & Pitts (1943). A logical calculus of the ideas immanent in nervous activity. Bull. Math. Biophysics 5, 115–133.

Frank Rosenblatt, 1957 — the Perceptron:
\[y = \text{sign}\!\left(\sum_i w_i x_i - \theta\right)\]
One neuron: \(N\) weighted inputs \(x_i\), one binary output \(y\). Unlike McCulloch-Pitts, the weights \(w_i\) are not fixed — they are learned from labelled examples via an error-correction rule.
The perceptron learning rule:
\[w_i \leftarrow w_i + \eta \cdot (y - \hat{y}) \cdot x_i\]
Every modern network still follows this principle.
Rosenblatt’s Mark 1 Perceptron (1958) was a physical hardware device — it made the front page of the New York Times.
Rosenblatt, F. (1958). The perceptron: a probabilistic model for information storage and organization in the brain. Psychol. Rev. 65(6), 386–408.
1969 — First winter Minsky & Papert prove perceptron cannot solve XOR.
1986 — Backpropagation Gradients through layers — the key mathematical unlock.
2012 — AlexNet cuts ImageNet error by 10 pp. Same idea, scaled. Deep learning era begins.
Energy, spins, and statistical mechanics
Hopfield networks · Boltzmann machines · the physics–ML bridge
John Hopfield, 1982 — stores memories as energy minima of the Ising Hamiltonian:
\[E = -\frac{1}{2} \sum_{ij} w_{ij} s_i s_j\]
Geoffrey Hinton extended this with the Boltzmann distribution \(P(s) \propto e^{-E/T}\) — giving neural networks a thermodynamic learning rule.
Nobel Prize in Physics 2024
“for foundational discoveries enabling machine learning with artificial neural networks”
— John J. Hopfield & Geoffrey E. Hinton
Awarded in Physics, not Computer Science.
A collective-effects view: a beam’s coherent modes emerge from pairwise particle interactions; a Hopfield memory emerges from pairwise neuron couplings. Both are many-body systems governed by an energy function.
The same mathematics describes Ising spins and neural networks:
| Ising model | Hopfield network |
|---|---|
| Spin \(s_i \in \{-1,+1\}\) | Neuron state |
| Coupling \(J_{ij}\) | Synaptic weight \(w_{ij}\) |
| \(E = -\tfrac{1}{2}\sum J_{ij}s_is_j\) | Same Hamiltonian |
| Ground state | Stored memory |
Red = −1 (spin ↓) · Cyan = +1 (spin ↑)
Hebbian learning — to store pattern \(\xi^\mu\): \[w_{ij} = \frac{1}{N}\sum_\mu \xi_i^\mu \xi_j^\mu\]
Hopfield, J.J. (1982). Neural networks and physical systems with emergent collective computational abilities. PNAS 79, 2554–2558.
| Physics concept | ML analogue |
|---|---|
| Hamiltonian / energy \(H(s)\) | Loss function \(\mathcal{L}(\theta)\) |
| Partition function \(Z = \sum_s e^{-H/T}\) | Normalisation constant (softmax, VAE) |
| Temperature \(T\) | Learning rate / annealing schedule |
| Boltzmann distribution \(P \propto e^{-E/T}\) | Softmax output — Boltzmann machine |
| Phase transition | Grokking (sharp generalisation threshold) |
| Langevin dynamics \(\dot{x} = -\nabla E + \eta\) | Stochastic gradient descent (SGD) |
| Free energy \(F = -T\ln Z\) | ELBO / variational lower bound (VAE) |
Not metaphors — the same mathematics, rediscovered independently. As accelerator physicists we already speak this language.
Part 2
AI in accelerators
A 30 years dive · Practical foundations Practical applications · Future directions & opportunities
From research curiosity to operational tool
Milestones · concrete results · living review
| Year | Facility | Approach | Impact |
|---|---|---|---|
| 1992–94 | Ion source · storage rings | Adaptive NN control (Mead+92); NN orbit correction (Bozoki & Friedman 94) | First ML for accelerator control |
| 2020 | SLAC LCLS | Bayesian optimisation in production | setup hours → ~tens of evaluations |
| 2020 | JLab CEBAF | ML on SRF cavity waveforms | Cavity-fault classification |
| 2024 | DESY / KIT | Cheetah differentiable simulator | 10³–10⁵× faster than tracking codes |
| 2020–23 | CERN · DESY | Reinforcement learning for control & tuning | Autonomous closed-loop control |
| 2024 | KIT KARA | Anomaly detection + energy management | −30% energy footprint on real ring |
Edelen et al., IEEE TNS 63 (2016) · Duris et al., PRL 124 (2020) · Kaiser et al., PRAB 27 (2024) · Ghribi et al., EPN 56 (2025)
Fault detection — production ML triage
Beam tuning — BO + RL, closed-loop
Simulation — differentiable, fast surrogates
Optimisation toolboxes — facility-agnostic
Tennant et al., PRAB 23 (2020) · Duris et al., PRL 124 (2020) · Kaiser et al., PRAB 27 (2024) · Ghribi et al., EPN 56 (2025) · Zhang et al., IPAC 2022 (Badger)
From raw signals to trained model
Data · Features · Method selection · Pipeline
① Data quality & preprocessing
① Clean Remove corrupted pulses, stuck sensors, dropouts.
② Synchronise Align timestamps across RF, BPM, power supplies.
③ Normalise Scale to [−1, 1] — prevents one channel dominating.
④ Balance SRF faults < 0.1 % of pulses — naïve model learns to ignore them.
A model trained on bad data learns bad behaviour.
No algorithm compensates for this.
② Feature engineering
this case: 256 IQ samples → 3 scalars · 99 % compression, fault information retained
The feature map depends on the data — RF waveform → FFT peaks · Q · phase drift · beam image → Gaussian-fit σ, centroid, image moments · bunch → 6-D phase-space stats (emittance, Twiss α/β) · BPM stream → orbit harmonics
Good features = simpler models + better generalisation.
Domain knowledge beats brute force.
③ Train & validate
The task decides what “validate” means — and which metric.
Same word, three protocols.
Anomaly & surrogate split the data; optimisation measures regret over a budget — there is no test set.
Train / validate / test — and the overfitting trap (supervised tasks)
One epoch = one complete forward + backward pass over every training sample.
Example: 10 000 pulses, batch 32 → 313 weight updates per epoch.
Training halts when validation loss stops improving — the dashed line marks that minimum (early stopping).
Validate on data the model has never seen — and on faults it has never seen.
Temporal splits matter more than random splits in accelerator data — and stratify to preserve the < 0.1 % fault rate.
④ Deploy & infer
① Latency budget Set by the control loop, not the model: fast feedback may need sub-ms, daily tuning tolerates seconds.
② Export & serve Deterministic runtime (e.g. ONNX, TorchScript) embeddable in EPICS / TANGO / DOOCS — no Python in the loop.
③ Compress to hardware Pruning + quantisation (FP32→INT8) cut latency — at some accuracy cost; trade-off depends on CPU / GPU / FPGA.
④ Monitor & retrain Track output rate and input distributions; retrain when drift crosses a threshold.
A model in a notebook is not a deployed model.
Latency, reliability and drift monitoring are engineering problems.
Three use cases · Three live notebooks
Use case 1 — RF fault detection · Use case 2 — Beam tuning · Use case 3 — Surrogate models
RF Fault Detection
Autoencoders on cavity IQ waveforms · Notebook 01
Every RF pulse → one IQ waveform per cavity at ~1 kHz. Clinical machines are safe by design — faults are rare.
Supervised classifiers need thousands of labelled fault examples per class — unavailable in clinical operation.
Solution: train on normal pulses only. Anomaly score = reconstruction error.
~1 kHz
IQ samples per cavity per second
1M+ pulses/day in clinical operation
| Class | Signature |
|---|---|
| Normal | Clean trapezoid · Q ≈ 0 |
| Quench | Sudden drop mid flat-top |
| Detuning | Off-resonance · large Q |
| Multipacting | Oscillations on flat-top |
No fault labels
needed
Anomaly detection trains on normal only
class RFAutoencoder(nn.Module):
def __init__(self, latent_dim=16):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv1d(2, 32, kernel_size=8, stride=4),
nn.GELU(),
nn.Conv1d(32, 64, kernel_size=4, stride=2),
nn.GELU(),
nn.Flatten(),
nn.Linear(64 * 32, latent_dim), # bottleneck
)
self.decoder = nn.Sequential(...) # mirrorclass RFAutoencoder(nn.Module):
def __init__(self, latent_dim=16):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv1d(2, 32, kernel_size=8, stride=4),
nn.GELU(),
nn.Conv1d(32, 64, kernel_size=4, stride=2),
nn.GELU(),
nn.Flatten(),
nn.Linear(64 * 32, latent_dim), # bottleneck
)
self.decoder = nn.Sequential(...) # mirrorclass RFAutoencoder(nn.Module):
def __init__(self, latent_dim=16):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv1d(2, 32, kernel_size=8, stride=4),
nn.GELU(),
nn.Conv1d(32, 64, kernel_size=4, stride=2),
nn.GELU(),
nn.Flatten(),
nn.Linear(64 * 32, latent_dim), # bottleneck
)
self.decoder = nn.Sequential(...) # mirrorclass RFAutoencoder(nn.Module):
def __init__(self, latent_dim=16):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv1d(2, 32, kernel_size=8, stride=4),
nn.GELU(),
nn.Conv1d(32, 64, kernel_size=4, stride=2),
nn.GELU(),
nn.Flatten(),
nn.Linear(64 * 32, latent_dim), # bottleneck
)
self.decoder = nn.Sequential(...) # mirrorclass RFAutoencoder(nn.Module):
def __init__(self, latent_dim=16):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv1d(2, 32, kernel_size=8, stride=4),
nn.GELU(),
nn.Conv1d(32, 64, kernel_size=4, stride=2),
nn.GELU(),
nn.Flatten(),
nn.Linear(64 * 32, latent_dim), # bottleneck
)
self.decoder = nn.Sequential(...) # mirrorInput (batch, 2, 256) — I and Q as two channels
① Encoder · lines 4–11 — two Conv1d stages compress the (2, 256) waveform.
② 1D convolutions · lines 5, 7 — capture local features (rise, drop, oscillations) with few parameters.
③ Bottleneck · line 10 — the pulse is squeezed through 16 numbers; the net must learn what normal looks like.
④ Decoder · line 12 — mirrors the encoder, rebuilding (2, 256) from the 16-D code.
Anomaly score = reconstruction error · Normal → low · Faults → high
◀ encoder · compress bottleneck decoder · reconstruct ▶
# Train on NORMAL pulses only
X_normal = X_train[y_train == 0]
for epoch in range(50):
for (batch,) in loader:
loss = ((batch - model(batch))**2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Score every pulse at inference
with torch.no_grad():
scores = ((X - model(X))**2).mean(dim=(1,2))
threshold = scores[normal_mask].quantile(0.95)# Train on NORMAL pulses only
X_normal = X_train[y_train == 0]
for epoch in range(50):
for (batch,) in loader:
loss = ((batch - model(batch))**2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Score every pulse at inference
with torch.no_grad():
scores = ((X - model(X))**2).mean(dim=(1,2))
threshold = scores[normal_mask].quantile(0.95)# Train on NORMAL pulses only
X_normal = X_train[y_train == 0]
for epoch in range(50):
for (batch,) in loader:
loss = ((batch - model(batch))**2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Score every pulse at inference
with torch.no_grad():
scores = ((X - model(X))**2).mean(dim=(1,2))
threshold = scores[normal_mask].quantile(0.95)# Train on NORMAL pulses only
X_normal = X_train[y_train == 0]
for epoch in range(50):
for (batch,) in loader:
loss = ((batch - model(batch))**2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Score every pulse at inference
with torch.no_grad():
scores = ((X - model(X))**2).mean(dim=(1,2))
threshold = scores[normal_mask].quantile(0.95)① Train on normal only · lines 1–2 — the model never sees a fault.
② Reconstruction loss · lines 3–8 — minimise the MSE between each pulse and its rebuild.
③ Unsupervised threshold · lines 10–13 — flag pulses above the 95th percentile of normal errors. No fault labels used.
1.000
ROC-AUC — on synthetic data
A detector needs a threshold — but which one? ROC-AUC sidesteps the choice.
Sweep every threshold; at each, plot true-positive rate (sensitivity) vs false-positive rate (1 − specificity). AUC = area under that curve.
0.5 → coin-flip 1.0 → perfect separation of normal vs fault.
Threshold-independent — the right metric when a sub-0.1 % fault rate makes any single fixed cut arbitrary.
Beam Tuning with Bayesian Optimisation
Cheetah · differentiable accelerator simulation · Notebook 02
Every morning on a clinical machine:
Currently: 20–45 min of physicist time, largely manual.
\(n\) correctors × \(k\) quads → high-dimensional space that grows exponentially for grid search.
Bayesian Optimisation fits naturally:
Demonstrated in production at SLAC LCLS and across light sources, FELs, and linacs (Duris 2020; Roussel et al. 2024).
A prior P(f) encodes your belief before data — e.g. “the beam response is smooth in magnet space”.
Bayes’ theorem updates it: P(f | data) ∝ P(data | f) · P(f) — the result is the posterior.
BO uses the posterior to pick the next probe: explore where uncertain, exploit where promising.
Every measurement actively informs the next — no wasted machine evaluations.
import torch, cheetah
# Beam diverges after a 2 m upstream drift
upstream = cheetah.Segment([cheetah.Drift(length=torch.tensor(2.0))])
beam_in = upstream.track(cheetah.ParameterBeam.from_twiss(
energy=torch.tensor(1088.3e6), # 150 MeV proton, eV
beta_x=torch.tensor(0.3),
emittance_x=torch.tensor(1e-6),
species=cheetah.Species("proton"),
))
# Re-focusing doublet
segment = cheetah.Segment(elements=[
cheetah.Drift(length=torch.tensor(0.1)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q1"),
cheetah.Drift(length=torch.tensor(0.6)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q2"),
cheetah.Drift(length=torch.tensor(0.3)),
cheetah.Screen(name="monitor"),
])
beam_out = segment.track(beam_in)import torch, cheetah
# Beam diverges after a 2 m upstream drift
upstream = cheetah.Segment([cheetah.Drift(length=torch.tensor(2.0))])
beam_in = upstream.track(cheetah.ParameterBeam.from_twiss(
energy=torch.tensor(1088.3e6), # 150 MeV proton, eV
beta_x=torch.tensor(0.3),
emittance_x=torch.tensor(1e-6),
species=cheetah.Species("proton"),
))
# Re-focusing doublet
segment = cheetah.Segment(elements=[
cheetah.Drift(length=torch.tensor(0.1)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q1"),
cheetah.Drift(length=torch.tensor(0.6)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q2"),
cheetah.Drift(length=torch.tensor(0.3)),
cheetah.Screen(name="monitor"),
])
beam_out = segment.track(beam_in)import torch, cheetah
# Beam diverges after a 2 m upstream drift
upstream = cheetah.Segment([cheetah.Drift(length=torch.tensor(2.0))])
beam_in = upstream.track(cheetah.ParameterBeam.from_twiss(
energy=torch.tensor(1088.3e6), # 150 MeV proton, eV
beta_x=torch.tensor(0.3),
emittance_x=torch.tensor(1e-6),
species=cheetah.Species("proton"),
))
# Re-focusing doublet
segment = cheetah.Segment(elements=[
cheetah.Drift(length=torch.tensor(0.1)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q1"),
cheetah.Drift(length=torch.tensor(0.6)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q2"),
cheetah.Drift(length=torch.tensor(0.3)),
cheetah.Screen(name="monitor"),
])
beam_out = segment.track(beam_in)import torch, cheetah
# Beam diverges after a 2 m upstream drift
upstream = cheetah.Segment([cheetah.Drift(length=torch.tensor(2.0))])
beam_in = upstream.track(cheetah.ParameterBeam.from_twiss(
energy=torch.tensor(1088.3e6), # 150 MeV proton, eV
beta_x=torch.tensor(0.3),
emittance_x=torch.tensor(1e-6),
species=cheetah.Species("proton"),
))
# Re-focusing doublet
segment = cheetah.Segment(elements=[
cheetah.Drift(length=torch.tensor(0.1)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q1"),
cheetah.Drift(length=torch.tensor(0.6)),
cheetah.Quadrupole(length=torch.tensor(0.3), name="Q2"),
cheetah.Drift(length=torch.tensor(0.3)),
cheetah.Screen(name="monitor"),
])
beam_out = segment.track(beam_in)① Beam setup · lines 3–10 — a 150 MeV proton beam; after a 2 m drift it has diverged.
② Re-focusing doublet · lines 12–21 — two independently powered quads (Q1, Q2) and a screen.
③ Track · line 22 — push the beam through; read σ at the screen.
Why Cheetah? PyTorch module → gradients flow through the simulation · ms per call · same API for a virtual or real machine.
At the screen, quads off: σ_x + σ_y = 13.2 mm (6.6 mm/plane) — the baseline BO must beat.
from skopt import gp_minimize
from skopt.space import Real
NOISE = 0.04 # 4% shot-to-shot noise
def objective(params):
k1_q1, k1_q2 = params
segment.Q1.k1 = torch.tensor(k1_q1)
segment.Q2.k1 = torch.tensor(k1_q2)
beam_out = segment.track(beam_in)
sigma = (beam_out.sigma_x + beam_out.sigma_y).item()
if not np.isfinite(sigma): return 0.20
return sigma * (1 + np.random.normal(0, NOISE))
result = gp_minimize(
func=objective,
dimensions=[Real(-15, 15, name="k1_Q1"),
Real(-15, 15, name="k1_Q2")],
n_calls=60, n_initial_points=12,
acq_func="EI", # Expected Improvement
)
# → ~2.2 mm (8-seed) vs 13.2 mm — a ~6× cutfrom skopt import gp_minimize
from skopt.space import Real
NOISE = 0.04 # 4% shot-to-shot noise
def objective(params):
k1_q1, k1_q2 = params
segment.Q1.k1 = torch.tensor(k1_q1)
segment.Q2.k1 = torch.tensor(k1_q2)
beam_out = segment.track(beam_in)
sigma = (beam_out.sigma_x + beam_out.sigma_y).item()
if not np.isfinite(sigma): return 0.20
return sigma * (1 + np.random.normal(0, NOISE))
result = gp_minimize(
func=objective,
dimensions=[Real(-15, 15, name="k1_Q1"),
Real(-15, 15, name="k1_Q2")],
n_calls=60, n_initial_points=12,
acq_func="EI", # Expected Improvement
)
# → ~2.2 mm (8-seed) vs 13.2 mm — a ~6× cutfrom skopt import gp_minimize
from skopt.space import Real
NOISE = 0.04 # 4% shot-to-shot noise
def objective(params):
k1_q1, k1_q2 = params
segment.Q1.k1 = torch.tensor(k1_q1)
segment.Q2.k1 = torch.tensor(k1_q2)
beam_out = segment.track(beam_in)
sigma = (beam_out.sigma_x + beam_out.sigma_y).item()
if not np.isfinite(sigma): return 0.20
return sigma * (1 + np.random.normal(0, NOISE))
result = gp_minimize(
func=objective,
dimensions=[Real(-15, 15, name="k1_Q1"),
Real(-15, 15, name="k1_Q2")],
n_calls=60, n_initial_points=12,
acq_func="EI", # Expected Improvement
)
# → ~2.2 mm (8-seed) vs 13.2 mm — a ~6× cutfrom skopt import gp_minimize
from skopt.space import Real
NOISE = 0.04 # 4% shot-to-shot noise
def objective(params):
k1_q1, k1_q2 = params
segment.Q1.k1 = torch.tensor(k1_q1)
segment.Q2.k1 = torch.tensor(k1_q2)
beam_out = segment.track(beam_in)
sigma = (beam_out.sigma_x + beam_out.sigma_y).item()
if not np.isfinite(sigma): return 0.20
return sigma * (1 + np.random.normal(0, NOISE))
result = gp_minimize(
func=objective,
dimensions=[Real(-15, 15, name="k1_Q1"),
Real(-15, 15, name="k1_Q2")],
n_calls=60, n_initial_points=12,
acq_func="EI", # Expected Improvement
)
# → ~2.2 mm (8-seed) vs 13.2 mm — a ~6× cutWhat the code does
① Objective · lines 6–13 — set the two quads, track in Cheetah, return σ_x + σ_y (with 4 % shot noise).
② gp_minimize · lines 15–21 — the BO loop: a GP surrogate + Expected-Improvement choose each next setting (60 calls, 12 random to start).
③ Result · line 22 — 13.2 → ~2.2 mm (8-seed mean), a ~6× cut in tens of evaluations.
Gaussian process = the model. Predicts a mean + variance at every untried setting; the kernel sets smoothness (Matérn 5/2 default).
Bayesian optimisation = the loop. Fit GP → maximise Expected Improvement (exploit low mean vs explore high variance) → evaluate → repeat.
GP = the engine (“what does it look like?”); BO = the driver (“where next?”).
Surrogate Models
Replacing tracking codes with neural networks · Notebook 03
Real-time control needs millisecond response — not minutes.
Workflow:
Typical speedup: 10³–10⁵× · error < 1% in domain
① Input → output · line 1 — two quad strengths (k₁) map to the two beam sizes (σ_x, σ_y).
② The network · lines 2–7 — a small MLP: three 128-wide Tanh hidden layers.
③ Training data · lines 8–9 — 5 000 Cheetah samples generated offline; 300 epochs with Adam.
Open problems · What comes next · Community
⚖ Explainability — EU AI Act: control-loop AI = high-risk.
🔁 Cross-facility transfer — Centre A model ≠ Centre B.
🔌 Agnostic integration — deploy one model into EPICS, TANGO or DOOCS; vendor-neutral adapter layer.
🗂 Data & model standards — no shared schema across facilities; FAIR ML models still missing.
🤖 Agentic orchestration — LLM agents coordinating multiple AI subsystems; emerging architecture.
EU AI Act 2024
High-risk class · SHAP / LIME audit
A → B gap
Few-shot domain adaptation: active research
EPICS · TANGO · DOOCS
vendor-neutral adapter layer
certification pipeline: active design work
FAIR + ONNX
Findable, Accessible, Interoperable, Reusable
ML-Exchange · open model registry emerging
Multi-agent
Fault-detect + tune + optimise, orchestrated
LLM planner → specialist ML tools
Active research — AI delivers value today.
SHAP: assigns each input a numerical contribution to the prediction (Shapley values from game theory, 1953 — model-agnostic).
LIME: fits a local linear model around one specific prediction to explain it locally.
EU AI Act (2024) classifies control-loop AI on clinical machines as high-risk — auditable, explainable decisions required.
In practice: SHAP tells you whether the model learned beam physics or a spurious data artefact.
→ XAI is developed in depth in the lecture proceedings.
Foundation models pretrain on multi-facility data · RL closes the loop on the real machine (CERN, DESY) · Generative models synthesise rare fault data · Digital twins = surrogate + live sensors, enabled by the ~600× speedup (→ TwinRISE)
AI-for-science missions
RAISE — EU “CERN for AI” · €107 M · Horizon Europe
GENESIS — US DOE · $293 M · 17 national labs
Hands-on — workshops & schools
RL4AA — workshop & challenge · Liverpool 2026
AISSAI Hackathon — Oct 11–16 2026 · IN2P3
Join the ARTIFACT network
ARTIFACT — 6 active initiatives · 14 countries · +40 partner institutions · 6 sister networks
€107 M
RAISE · Horizon Europe 2025–27
Resource for AI in Science in Europe
ec.europa.eu → RAISE
$293 M
GENESIS · US DOE · 17 national labs · 2025
energy.gov → Genesis
RL4AA · Liverpool 2026
Tutorials · challenge · open datasets
rl4aa.github.io
AISSAI · Oct 11–16 2026
AI for Particle Accelerators hackathon
indico.in2p3.fr/event/39704 · slides
Shares data · beam-time · models · training material
Multi-project synergies — TwinRISE · IRIS · SCIANCE · ARTIFACT Doctoral Networks
This work has received funding from the European Union’s Horizon Europe research & innovation programme — grant agreements No. 101275935 (iRIS), 101287548 (TwinRISE) & 101293570 (SCIANCE) — and from national & EU partner programmes.
AI is broader than deep learning. We built three methods end to end — two neural, one classical (BO/GP) — on real beam-physics problems: problem → method → result → runnable notebook.
All code open & runnable on Colab — github.com/aghribi/cas2026-ai-medical-accelerators
Key papers — cited in this lecture
Ghribi et al., EPN 56(1), 15 (2025)
Roussel et al., PRAB 27, 084801 (2024)
Kaiser et al., PRAB 27, 054601 (2024)
Duris et al., PRL 124, 124801 (2020)
Tennant et al., PRAB 23, 114601 (2020)
Hopfield, PNAS 79, 2554 (1982)
Living review — aghribi.github.io/acc-ml-living-review
Notebooks on Colab
01 · RF Fault Detection
02 · Beam Tuning
03 · Surrogate Model
RL4AA community
rl4aa.github.io — workshops · tutorials · challenge
AI — Artificial Intelligence
ML — Machine Learning
NN — Neural Network
MLP — Multi-Layer Perceptron
CNN — Convolutional Neural Network
LSTM — Long Short-Term Memory
AE — Autoencoder
VAE — Variational Autoencoder
RL — Reinforcement Learning
PINN — Physics-Informed Neural Network
SVM — Support Vector Machine
BO — Bayesian Optimisation
GP — Gaussian Process
EI — Expected Improvement
LHS — Latin Hypercube Sampling
PCA — Principal Component Analysis
UQ — Uncertainty Quantification
MC — Monte Carlo (dropout)
ROC — Receiver Operating Characteristic
AUC — Area Under the ROC Curve
MSE / MAE — Mean Squared / Absolute Error
R² — Coefficient of Determination
XAI — Explainable AI
SHAP — SHapley Additive exPlanations
LIME — Local Interpretable Model-agnostic Explanations
FAIR — Findable, Accessible, Interoperable, Reusable
ONNX — Open Neural Network Exchange
IQ — In-phase / Quadrature (RF)
SRF — Superconducting Radio-Frequency
BPM — Beam Position Monitor
EPICS · TANGO · DOOCS — control systems
TRL — Technology Readiness Level
Thank you · Questions welcome
Dr. Adnan Ghribi · GANIL / CNRS-IN2P3 · adnan.ghribi@ganil.fr
① Ball in a valley — memories are energy minima. Noisy input rolls downhill → arrives at nearest stored pattern.
② Spin update — \(s_i \leftarrow \text{sign}(\sum_j w_{ij} s_j)\) Energy decreases at every step: guaranteed convergence.
③ Capacity ≈ 0.14 N — beyond this, retrieval fails catastrophically (sharp phase transition, not gradual).
si ← sign(Σ wij sj)
Hebb rule · ΔE ≤ 0 per step
Same physics as spin relaxation
0.14 N
Capacity limit · recall error: 0 → 50%
Amit, Gutfreund & Sompolinsky (1985). Storing infinite numbers of patterns in a spin-glass model. PRL 55, 1530.
1969 — Perceptrons (Minsky & Papert) proved a single-layer perceptron cannot compute XOR, nor any non-linearly-separable function.
Impact as much sociological as mathematical:
The irony
XOR is solvable — with one hidden layer. The authors knew it, but wrongly argued such nets could never be trained at scale. Backprop proved them wrong 17 years later.
One influential “impossibility” result can freeze a direction for a decade. Scepticism cuts both ways.
Minsky & Papert (1969). Perceptrons, MIT Press. · Werbos (1974) · Rumelhart, Hinton & Williams (1986), Nature 323, 533.
Expected Improvement acquisition function:
\[\alpha_{\text{EI}}(x) = \mathbb{E}\big[\max(0,\; f_{\text{best}} - f(x))\big]\]
For a GP posterior with mean \(\mu(x)\), std \(\sigma(x)\), and \(z = \dfrac{f_{\text{best}} - \mu(x)}{\sigma(x)}\):
\[\alpha_{\text{EI}}(x) = \sigma(x)\,\big[\,z\,\Phi(z) + \phi(z)\,\big]\]
The kernel is the prior on smoothness
Kernel choice encodes physics — the single most consequential modelling decision in BO.
Controlled experiment (notebook 02 §7b): a D-quad matching section, the same budget B = 120 evaluations for every method, log(σ_x+σ_y) objective, 4% noise, 3 seeds.
| Dimension | Grid | Random | Bayesian opt. |
|---|---|---|---|
| D = 2 (2 quads) | 5.1 mm | 4.7 mm | 3.4 mm |
| D = 4 (4 quads) | 7.2 mm | 3.0 mm | 1.2 mm |
| D = 6 (6 quads) | 5.9 mm | 4.6 mm | 1.5 mm |
At D = 6, BO is ~3× better than either baseline and every seed wins. A full-resolution 7ᴰ grid would need 7⁶ = 117,649 evaluations; BO used 120.
Why grid/random collapse: usable volume shrinks as ~rᴰ, so a fixed budget covers exponentially less of the space. BO builds a surrogate and spends evaluations where they matter. And note D = 4/6 reach a smaller spot than D = 2 — more quads give more focusing freedom, which BO exploits and classical methods cannot.
| Approach | Needs fault labels? | Detects unseen fault types? | Fit for clinical RF |
|---|---|---|---|
| Threshold / interlock | no | no — only hard-coded limits | safety net, not detection |
| Supervised classifier (Tennant 2020) | yes — many per class | no | great when labels exist |
| Autoencoder (ours) | no — normal data only | yes | fits label-scarce clinical ops |
The autoencoder learns “normal”; anything it reconstructs poorly is flagged — including fault modes never seen in training. A supervised classifier can only recognise the classes it was trained on.
Trade-off: the autoencoder tells you something is wrong, not what. Pair it with a classifier once labels accumulate.
Two ways to make the MLP surrogate report confidence (notebook 03):
Deep ensemble — train N models from different initialisations; the spread of their predictions is the uncertainty.
MC-dropout — keep dropout active at inference and run K stochastic forward passes.
Measured — MC-dropout, σ_x+σ_y
Epistemic (reducible by more data) vs aleatoric (irreducible noise). For control you want calibrated σ — the error bars should match the actual errors.
Setup: the agent observes machine state \(s\) (BPMs, losses), takes action \(a\) (corrector / quad set-points), receives reward \(r\) (e.g. −beam loss), and learns a policy \(\pi(a\,|\,s)\) that maximises cumulative reward.
Why RL, not BO, here? RL amortises: it learns a reusable policy for a recurring sequential task, where BO re-optimises from scratch every time.
Demonstrated
Open problems on a clinical machine: sample efficiency and safe exploration.
Kain et al., PRAB 23, 124801 (2020) · Hirländer et al. (2020) · Kaiser et al. (2023)
The EU AI Act (2024) is risk-tiered. AI in the control loop of a medical device falls in the high-risk class, which requires:
Practical consequences
This is why “AUC = 1.0 in a notebook” is the start, not the end. Certification, not accuracy, is the bottleneck to clinical deployment.
This lecture: Autoencoder — unsupervised, normal-data only, scales to 1 kHz waveform streams.
Duris et al., PRL 124 (2020) · Roussel et al., PRAB 27 (2024) · Emma et al., Nat. Mach. Int. 1 (2019) · Kain et al., Phys. Rev. Accel. Beams 23 (2020)
Edelen et al., IEEE TNS 63 (2016) · Kaiser et al., PRAB 27 (2024)
Epistemic (model) uncertainty: caused by insufficient data — reducible by collecting more samples.
Aleatoric (data) uncertainty: inherent measurement noise — irreducible.
Methods: deep ensembles (spread of N models) · MC Dropout (stochastic inference) · Gaussian processes (closed-form variance).
Knowing when the model is uncertain is as important as the prediction itself — especially for safety-critical beam control.
CAS 2026 · Jurmala · AI for Medical Accelerators — Accelerator Side · A. Ghribi