File size: 7,928 Bytes
3046edb
 
256b4e9
 
 
 
 
 
 
 
 
3046edb
256b4e9
 
 
 
 
 
 
52366e1
 
 
256b4e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14db1c8
 
 
256b4e9
 
 
 
 
 
 
 
 
 
 
 
 
 
52366e1
 
256b4e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14db1c8
 
 
 
 
 
 
 
256b4e9
 
 
 
 
 
 
 
 
 
 
 
14db1c8
 
256b4e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
---
license: mit
library_name: pytorch
pipeline_tag: tabular-regression
tags:
  - set-transformer
  - chemberta
  - radiation-shielding
  - x-ray
  - materials-science
  - monte-carlo-surrogate
---

# STAR-XPC

STAR-XPC predicts how much of an X-ray beam passes through a polymer composite
shield. You give it a polyethylene slab loaded with fillers, a slab thickness
and a tube voltage. It returns the transmission ratio **I/I₀** in milliseconds. 
The penEasy (PENELOPE) Monte Carlo simulations it was trained to replace take 
hours per point. The training data were generated with
[attenupy](https://github.com/cmpe-netlab41/attenupy), which designs, runs and
collects these penEasy shielding studies.

The composite is treated as a *set* of ingredients. Each ingredient is described
by a ChemBERTa embedding of its SMILES and its weight fraction, and a Set
Transformer maps the set plus four physics features to I/I₀. Because the
chemistry enters through SMILES, you can also register materials the model was
not trained on.

## Quickstart

```bash
pip install torch numpy safetensors huggingface_hub
```

```python
import sys
from huggingface_hub import snapshot_download

repo = snapshot_download("cmpe-netlab41/STAR-XPC")
sys.path.insert(0, repo)
from star_xpc import STARXPC

model = STARXPC.from_pretrained(repo)

# 40 wt% Bi + 30 wt% W in polyethylene (the remaining 30 wt%), 80 kVp beam
y = model.predict({"Bi": 0.40, "W": 0.30}, thickness_mm=[0.25, 0.5, 1.0, 1.5], kvp=80)
print(y)  # I/I0 at each thickness
```

`predict` works out the composite's density and mean excitation energy from the
composition, so the composition, thickness and voltage are all you need.

## Inputs

| Argument | Meaning | Unit |
|---|---|---|
| `fillers` | Filler weight fractions of the **whole** composite, e.g. `{"Bi2O3": 0.4, "BaSO4": 0.4}`. Up to 10 fillers | fraction (0.4 = 40 wt%) |
| `thickness_mm` | Slab thickness, scalar or array | mm |
| `kvp` | X-ray tube voltage, scalar or array (broadcast with thickness) | kVp |
| `polymer_fraction` | Polyethylene weight fraction. Defaults to `1 - sum(fillers)` | fraction |
| `mass_den` | Composite density. Computed if omitted | g/cm³ |
| `mean_excit` | Composite mean excitation energy. Computed if omitted | eV |

## Materials

The model ships with the materials it was trained on:

- **Fillers:** B, B4C, BN, Ba, BaSO4, Bi, Bi2O3, Cu, Sb, Sn, W, WO3
- **Matrix:** polyethylene (C2H4)

### Registering a new material

Any other filler can be registered from its SMILES. It is embedded with ChemBERTa
(`seyonec/ChemBERTa-zinc-base-v1`, pinned revision) using exactly the recipe the
training materials went through. For example, lead and gadolinium, which are not
in the training data:

```python
# pip install transformers
model.add_material("Pb", smiles="[Pb]", density=11.34)
model.add_material("Gd", smiles="[Gd]", density=7.90)

model.predict({"Pb": 0.70}, thickness_mm=[0.3, 0.6, 1.2], kvp=100)
model.predict({"Gd": 0.40, "W": 0.30}, thickness_mm=1.0, kvp=120)
```

Registered this way, Pb and Gd reach NMSE % 0.19 and 0.20 against penEasy at
100 kVp. That is somewhat above the held-out sets of trained materials, which
range from 0.01 to 0.13 (see [results](#held-out-test-results)).

- **Formula:** read from the name when the name is a chemical formula (`"Pb"`,
  `"Gd2O3"`). Otherwise pass `formula="..."`.
- **Density:** in g/cm³, used to compute the composite density. Without a
  formula or density, pass `mass_den=` and `mean_excit=` to `predict` instead.
- **Style:** write SMILES the way the training materials were written. Use pure
  elements as bracket atoms (`[Pb]`), oxides as covalent structures
  (`O=[Gd]O[Gd]=O`, `O=[W](=O)=O`), and salts as ions
  (`[Ba++].[O-]S(=O)(=O)[O-]`). Keep SMILES under 64 tokens, since longer ones
  are truncated and you get a warning.
- **Unregistered names:** using one raises an error that shows the
  `add_material` call to make.
- **Accuracy:** `predict` warns once for each material that is not in the
  training data. Treat those predictions as estimates and confirm important
  ones with Monte Carlo. [attenupy](https://github.com/cmpe-netlab41/attenupy)
  sets up and runs those simulations.

## Physics features

- **Mean excitation energy:** the Bragg additivity rule with PENELOPE's elemental
  I-values for Z = 1–99, so it also works for registered materials. This is what
  PENELOPE's `material` program does, and it matches the training data to within
  10⁻⁴.
- **Density:** the inverse rule of mixtures. It uses the training densities for
  the bundled materials and the density you pass for registered ones.
- **If you know better:** some boron-containing training samples used a slightly
  different boron density. If you have the exact values from PENELOPE, pass
  `mass_den=` and `mean_excit=`.

## Batch prediction from a table

`predict_frame` takes a pandas DataFrame in the training-data schema:
`molecule_1..10`, `ratio_1..10` (filler wt%), `polymer`, `polymer_ratio`
(fraction), `geo_number_mm` (**in units of 0.01 mm**, so 100 = 1 mm), `spc`
(kVp), `mass_den` and `mean_excit`.

```python
import pandas as pd
y = model.predict_frame(pd.read_csv("my_samples.csv"))
```

## Training domain and limitations

- **Target:** the energy deposited in an air detector behind the slab, divided
  by the same quantity with the slab replaced by air. It comes from penEasy
  simulations of a fixed slab-and-detector geometry.
- **Beams:** four spectra. 30 kVp is ISO N-30, 80 kVp is IEC RQR6, 100 kVp is
  IEC RQR8 and 120 kVp is IEC RQR9. Other voltages are interpolation between
  those spectra and are untested.
- **Thickness:** 0.15–2.5 mm.
- **Composition:** polyethylene at 20–40 wt%, with one to several fillers making
  up the remaining 60–80 wt%.
- **Output range:** the output layer is unbounded, so predictions near 0 or 1
  can land slightly outside that range.
- **Warnings:** `predict` warns when an input falls outside the training domain.

## Held-out test results

None of these test sets were used in training. Each has 10 thicknesses.

| Test set | NMSE % | RMSE | MAPE (10 % trimmed) |
|---|---:|---:|---:|
| Bi–W, 80 kVp | 0.129 | 0.0109 | 6.86 % |
| W–Cu–B, 100 kVp | 0.00724 | 0.0042 | 0.65 % |
| Bi–Sb, 120 kVp | 0.0469 | 0.0106 | 2.65 % |
| Bi₂O₃–BaSO₄, 80 kVp | 0.0865 | 0.0095 | 4.89 % |
| BaSO₄–WO₃, 100 kVp | 0.0167 | 0.0064 | 1.78 % |
| Bi₂O₃, 120 kVp | 0.0113 | 0.0053 | 1.22 % |
| **Pb** 70 wt% (registered), 100 kVp | 0.192 | 0.0167 | 5.24 % |
| **Gd** 70 wt% (registered), 100 kVp | 0.198 | 0.0195 | 4.03 % |

NMSE % = 100 · MSE / mean(y²).

## How it works

Each composite becomes a set of 11 slots (up to 10 fillers plus the polymer,
zero-padded). Each slot is a 773-d vector:

- the ingredient's 768-d ChemBERTa embedding, which is the mean of the last 4
  hidden layers, masked-mean-pooled over tokens and L2-normalised;
- its share of the composition;
- four z-scored global features: tube voltage, composite density, thickness and
  mean excitation energy. The z-score statistics are those of the model's
  training split and are stored in `config.json`.

A Set Transformer (2 ISAB blocks with 64 inducing points, PMA with 8 seeds and a
SAB; width 256, 32 heads) turns the set into a single output.

In the training data, filler amounts were stored in wt% and the polymer amount
as a fraction, and the two were renormalised together. `star_xpc.py`
reproduces that encoding, so give `predict` ordinary weight fractions.

## Files

| File | Contents |
|---|---|
| `star_xpc.py` | Model definition and the `STARXPC` predictor |
| `model.safetensors` | Set Transformer weights |
| `embeddings.safetensors` | ChemBERTa embeddings of the 13 training materials |
| `config.json` | Architecture, feature normalisation statistics, material table, training domain |

## Citation

<!-- TODO: add the paper's BibTeX once it is available -->