Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,10 @@ those diameters is a search, and `halbach-optimizer` runs it:
./build/optimizer/halbach-optimizer -o output/halbach_optimized.json
```

Seven worked designs live in `designs/`, one folder each, holding the configuration, the resulting
magnet, the field it produces and the convergence of the search. `designs/regenerate.sh` rebuilds
them all.

With no configuration file the defaults are the design of `HalbachOptimisation/`, the genetic
algorithm this is a port of: 23 rings 22 mm apart, each position choosing one of 19
(radius, magnet count) pairs, in two layers 21 mm apart, measured over a 200 mm sphere.
Expand All @@ -454,7 +458,7 @@ ignore. That object is also a configuration file, so the output of one run is th
next:

```bash
./build/optimizer/halbach-optimizer -c output/halbach_optimized.json --seed 7
./build/optimizer/halbach-optimizer -c designs/nmr-5ring-10mm-tube/optimized.json --seed 7
```

#### How it works
Expand Down Expand Up @@ -482,8 +486,8 @@ another position changes. So the run has three stages, and only the middle one i

#### Configuration

`halbach_optimization.json` in the project root is the full set of defaults, written out. Every
key is optional:
`designs/imaging-200mm-dsv/config.json` is the full set of defaults, written out. Every key is
optional, and every design under `designs/` carries its own — see `designs/README.md`:

| Key | Meaning |
| --- | --- |
Expand All @@ -500,8 +504,9 @@ key is optional:

The command line overrides the file: `--generations`, `--population`, `--seed`, `--symmetry`,
`--objective`, `--field-model`. `--emit magnets` writes every magnet out one by one instead of the
46 rings. `--history convergence.csv` writes the convergence curve. `--no-verify` skips stage
three, for a smoke test. `halbach-optimizer --help` lists them all.
46 rings. `--history convergence.csv` writes the convergence curve, and `--field field.csv` writes the field
the verification measured, one row a sample, so the answer can be plotted or a lineshape fitted
without simulating it again. `--no-verify` skips stage three, for a smoke test. `halbach-optimizer --help` lists them all.

`--field-model dipole` replaces each cube with the point dipole of the same moment,
`m = V·Br/µ0`, which is the model `HalbachOptimisation/halbachFields.py` uses. It is there to
Expand Down
109 changes: 109 additions & 0 deletions designs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Magnet designs

One folder per magnet. Each was produced by `halbach-optimizer` from the
`config.json` beside it, and each folder holds both the answer and the evidence
for it.

| file | what it is |
| --- | --- |
| `config.json` | what was asked for. The `_`-prefixed keys are the record of *why* each parameter is what it is — the sweeps behind it, the constraints that bind, and what was traded against what. The optimizer ignores them. |
| `optimized.json` | what came out. **An input file the simulator runs as it stands** — `arrangements` plus a `field_of_view` — with the homogeneity and field figures beside them in a `halbach_optimization` object the readers ignore. |
| `field.csv` | the field the verification measured, one row a sample: `x,y,z,Bx,By,Bz,Bmag` over the whole sample sphere, at nine significant digits. |
| `convergence.csv` | the search, one row a generation: `generation,best_ppm,best_ever_ppm,mean_ppm,seconds`. |

## Where the homogeneity and field figures live

In `optimized.json`, under `halbach_optimization`. Two blocks, and the
difference between them matters:

```json
"optimized": { "homogeneity_ppm": 90.4, "mean_T": 0.2127, "min_T": ..., "max_T": ...,
"peak_to_peak_T": ..., "points": 4662 },
"verified": { "homogeneity_ppm": 91.4, "mean_T": 0.2127, ..., "points": 33401 }
```

`optimized` is the figure the search was steered by, over whatever symmetry
reduction it used. `verified` is the same figure taken again over the **whole**
sphere, from the magnets themselves, through the simulator the rest of the
project uses. **Quote `verified`.** A number that was optimized cannot also be
the evidence that the optimization worked, and the two do come apart: on
`nmr-3ring-21magnets` the octant search said 531 ppm where the honest figure was
719, because a 3-magnet ring carries a harmonic the octant reflections do not
respect. Everything with ten or more magnets to a ring agrees to about 1%.

Alongside them: `basis` (the size of the precomputation), `magnet_count`,
`genome`, `chosen` (which candidate went to which ring position, with radii and
counts), and `timing_s`.

## The designs

Field figures are `verified`, over the whole sample sphere.

| design | magnets | cube | field | homogeneity | DSV | tube |
| --- | ---: | ---: | ---: | ---: | ---: | --- |
| `imaging-200mm-dsv` | 2971 | 12 mm | 49.1 mT (2.09 MHz) | 805 ppm | 200 mm | — imaging |
| `nmr-21ring-10mm-tube` | 789 | 8 mm | 316.0 mT (13.46 MHz) | 215 ppm | 20 mm | 10 mm |
| `nmr-5ring-10mm-tube` | 185 | 8 mm | 212.7 mT (9.05 MHz) | **91 ppm** | 12 mm | 10 mm |
| `nmr-3ring-5mm-tube` | 99 | 8 mm | 178.6 mT (7.60 MHz) | 247 ppm | 8 mm | 5 mm |
| `nmr-2ring-5mm-tube` | 86 | 8 mm | 82.8 mT (3.52 MHz) | 246 ppm | 6 mm | 5 mm |
| `nmr-3ring-21magnets` | 21 | 24 mm | 171.2 mT (7.29 MHz) | 602 ppm | 4 mm | capillary |
| `nmr-2ring-10magnets` | 10 | 24 mm | 83.9 mT (3.57 MHz) | 9396 ppm | 4 mm | — not usable |

`imaging-200mm-dsv` is the port of `HalbachOptimisation/`, the Python project
this optimizer replaces. The rest are NMR magnets for a sample tube along the
bore, with B0 transverse to it.

## Reading the ppm figure

Peak-to-peak over a sphere is a worst-case number and it **overstates the line
you would actually see**, often by a lot. What is left after optimization is
mostly ring-discreteness ripple, which grows as (r/R)^(N-2) — for a 15-magnet
ring that is r¹³, so almost all of it lives in a thin shell at the very edge of
the sphere. On `nmr-5ring-10mm-tube`, half the volume sits within 4.8 ppm of the
mean while the peak-to-peak is 91.

The lineshape is just the histogram of `Bmag` over the detected volume, so
`field.csv` gives it directly:

| design | p-p ppm | line FWHM | as ppm | T2\* |
| --- | ---: | ---: | ---: | ---: |
| `nmr-21ring-10mm-tube` | 215 | 96 Hz | 7.2 | 3.3 ms |
| `nmr-5ring-10mm-tube` | 91 | 48 Hz | 5.3 | 6.6 ms |
| `nmr-3ring-5mm-tube` | 247 | 78 Hz | 10.3 | 4.1 ms |
| `nmr-2ring-5mm-tube` | 246 | 22 Hz | 6.2 | 14.7 ms |
| `nmr-3ring-21magnets` | 602 | 1024 Hz | 140 | 0.31 ms |
| `nmr-2ring-10magnets` | 9396 | 5878 Hz | 1644 | 0.05 ms |

Against a probe dead time of 10–50 µs, every T2\* above 1 ms loses well under a
percent of the FID. None of these has the ~1 ppm linewidth chemical shift needs,
which is what shimming is for; and none of it survives temperature drift, since
NdFeB moves about −1100 ppm/K.

## Regenerating

```bash
./regenerate.sh # every design
./regenerate.sh nmr-5ring-10mm-tube # one of them
./regenerate.sh --check # rebuild into a temp dir and compare, changing nothing
```

`--check` compares the genome and the `verified` figures, not the timings.
Every design here reproduces exactly for its seed and thread count; across
different thread counts the genetic operators draw from a thread-partitioned
random pool and float sums reassociate, so the answers agree on the design but
need not agree in the last digit.

The `field.csv` files are about 3 MB each, 22 MB in total. They are
reproducible from `config.json` in seconds to a couple of minutes, so add
`designs/*/field.csv` to `.gitignore` if you would rather not carry them.

## Running one

```bash
./build/standalone/Greeter -i designs/nmr-5ring-10mm-tube/optimized.json \
-s designs/nmr-5ring-10mm-tube/snapshot.json
```

The snapshot opens in the viewer. `optimized.json` needs no editing — the
metadata block is ignored by the readers, and the `field_of_view` is already the
sample sphere's bounding box.
59 changes: 59 additions & 0 deletions designs/imaging-200mm-dsv/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"halbach_optimization": {

"_comment": "Every value here is the default, so an optimizer run with no configuration file at all is this file. It is the design of HalbachOptimisation/homogeneityOptimisation.py: a cylinder of 23 rings 22 mm apart, each position choosing one of 19 (radius, magnet count) pairs, in two layers 21 mm apart.",

"ring_count": 23,
"ring_separation": 0.022,

"candidates": [
{"radius": 0.148, "count": 50},
{"radius": 0.151, "count": 51},
{"radius": 0.154, "count": 52},
{"radius": 0.156, "count": 53},
{"radius": 0.159, "count": 54},
{"radius": 0.162, "count": 55},
{"radius": 0.165, "count": 56},
{"radius": 0.168, "count": 57},
{"radius": 0.171, "count": 58},
{"radius": 0.174, "count": 59},
{"radius": 0.177, "count": 60},
{"radius": 0.180, "count": 61},
{"radius": 0.183, "count": 62},
{"radius": 0.186, "count": 63},
{"radius": 0.189, "count": 64},
{"radius": 0.192, "count": 65},
{"radius": 0.195, "count": 66},
{"radius": 0.198, "count": 67},
{"radius": 0.201, "count": 68}
],

"outer_radius_offset": 0.021,
"outer_count_offset": 7,

"order": 1,

"element": {
"size": 0.012,
"remanence": 1.3
},

"dsv": 0.2,
"resolution": 0.005,

"symmetry": "octant",
"objective": "ppm_bx",
"field_model": "cuboid",

"genetic": {
"population": 10000,
"generations": 100,
"cx_prob": 0.55,
"mut_prob": 0.4,
"gene_mut_prob": 0.05,
"tournament": 3,
"elitism": 1,
"seed": 42
}
}
}
102 changes: 102 additions & 0 deletions designs/imaging-200mm-dsv/convergence.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
generation,best_ppm,best_ever_ppm,mean_ppm,seconds
0,8101.52,8101.52,136435,7.3e-08
1,7849.12,7849.12,90612.5,0.202187
2,7461.71,7461.71,69850.3,0.197998
3,5674.92,5674.92,57813.9,0.204368
4,5186.34,5186.34,49662.2,0.209631
5,4779.94,4779.94,44547.3,0.209373
6,3283.93,3283.93,40454.3,0.246036
7,1974.54,1974.54,38118.9,0.19608
8,1974.54,1974.54,34980.2,0.22197
9,1974.54,1974.54,32737,0.210893
10,1974.54,1974.54,30882.5,0.190113
11,1974.54,1974.54,29640.9,0.221356
12,1974.54,1974.54,29505.5,0.205366
13,1974.54,1974.54,28744.8,0.203065
14,1974.54,1974.54,28184.3,0.21039
15,1974.54,1974.54,27868.5,0.214157
16,1974.54,1974.54,27315.4,0.203219
17,1974.54,1974.54,27648.9,0.199746
18,1312.29,1312.29,26738.1,0.22743
19,1312.29,1312.29,26422.5,0.209309
20,1312.29,1312.29,25514.4,0.262848
21,907.155,907.155,26146.8,0.217538
22,907.155,907.155,24771.9,0.230303
23,907.155,907.155,24689.3,0.233633
24,907.155,907.155,24079.5,0.276177
25,907.155,907.155,22755.8,0.234103
26,907.155,907.155,22347.6,0.209855
27,907.155,907.155,21622.7,0.266339
28,907.155,907.155,21059.3,0.272571
29,842.94,842.94,19912.2,0.265702
30,806.439,806.439,18900.1,0.221049
31,806.439,806.439,17564.1,0.245743
32,806.439,806.439,17029.7,0.24296
33,806.439,806.439,14930.5,0.337188
34,806.439,806.439,14745.6,0.325866
35,806.439,806.439,12782.7,0.280995
36,806.439,806.439,11514.9,0.224355
37,806.439,806.439,10119.1,0.204933
38,806.439,806.439,9513.87,0.283719
39,806.439,806.439,9128.96,0.262237
40,794.036,794.036,9267.33,0.277736
41,794.036,794.036,9428.34,0.205629
42,794.036,794.036,9704.23,0.226392
43,794.036,794.036,9449.83,0.179688
44,794.036,794.036,9333.96,0.193564
45,794.036,794.036,8945.12,0.182924
46,794.036,794.036,9246.31,0.190069
47,794.036,794.036,9595.05,0.213226
48,794.036,794.036,9406.76,0.179147
49,794.036,794.036,9423.32,0.20157
50,794.036,794.036,9456.54,0.208474
51,794.036,794.036,9677.27,0.205819
52,794.036,794.036,9208.88,0.232321
53,794.036,794.036,9383.96,0.232358
54,794.036,794.036,9616.24,0.235049
55,794.036,794.036,9287.4,0.260572
56,794.036,794.036,9158.29,0.18533
57,794.036,794.036,9264.26,0.214221
58,794.036,794.036,9354.95,0.181662
59,794.036,794.036,9458.93,0.172563
60,794.036,794.036,9440.98,0.184434
61,794.036,794.036,8932.82,0.204836
62,794.036,794.036,9592.97,0.211893
63,794.036,794.036,9339.08,0.190144
64,794.036,794.036,9303.53,0.182467
65,794.036,794.036,9303.94,0.192508
66,794.036,794.036,9445.74,0.211745
67,794.036,794.036,9932.23,0.204642
68,794.036,794.036,9408.34,0.184112
69,794.036,794.036,9199.08,0.189697
70,794.036,794.036,9245.36,0.18813
71,794.036,794.036,9481.58,0.238724
72,794.036,794.036,9326.61,0.275781
73,794.036,794.036,9587.35,0.24268
74,794.036,794.036,9399.89,0.198247
75,794.036,794.036,9683.95,0.190395
76,794.036,794.036,8892.99,0.212416
77,794.036,794.036,9325.38,0.190637
78,794.036,794.036,8853.66,0.190838
79,794.036,794.036,9780.71,0.189715
80,794.036,794.036,9323.76,0.19414
81,794.036,794.036,9255.59,0.211951
82,794.036,794.036,8741.57,0.207375
83,794.036,794.036,9066.54,0.191849
84,794.036,794.036,9082.9,0.197612
85,794.036,794.036,9900.35,0.17581
86,794.036,794.036,8826.4,0.2119
87,794.036,794.036,9418.23,0.213331
88,794.036,794.036,9819.57,0.194381
89,794.036,794.036,9661.89,0.184161
90,794.036,794.036,9152.66,0.193908
91,794.036,794.036,9900.45,0.203714
92,794.036,794.036,9691.71,0.219189
93,794.036,794.036,9422.76,0.185709
94,794.036,794.036,9715.7,0.180585
95,794.036,794.036,9548.24,0.197988
96,794.036,794.036,9482.2,0.198148
97,794.036,794.036,9332.02,0.214273
98,794.036,794.036,9206.61,0.19632
99,794.036,794.036,9419.78,0.195931
100,794.036,794.036,9062.5,0.192782
Loading
Loading