What you can do
You can find out which of your augmentations are copies and which are new samples, before you train anything on them. The test is short and it runs on your own structure.
- POST /api/v1/flip, /api/v1/rotate, /api/v1/tile, /api/v1/crop and /api/v1/coarsen each return a new structureId, so the transformed copy is ready to measure without uploading anything.
- POST /api/v1/metrics and POST /api/v1/surface-area say whether the geometry actually changed.
- POST /api/v1/conductivity with method "graph-network" gives the label, along with meta.converged and meta.connectedPath.
- Three seeds of the same recipe give you the yardstick: the spread between them is how much the label moves for a genuinely different sample.
Transforms that leave the geometry untouched should not need a new label. The run below shows what happens when you compute one anyway.
Why it matters
A surrogate model needs more labelled structures than anyone wants to generate and solve. Flipping, rotating and cropping the handful you have is the usual way to close that gap.
The gap only closes if the label survives the transform. A mirrored packing is the same material, so it belongs in the training set with the same number attached.
Suppose you solve the mirrored copy instead and get a different answer. You have just taught the model that mirroring changes the physics.
How far the label is allowed to move is not a matter of taste. It has to be smaller than the difference you want the model to learn. The natural yardstick is how far the label moves between two seeds of the same recipe.
What was run
Two structures were built from the API example recipes in a 64³ box at 1 µm per voxel, on three seeds each.
- Isotropic spheres — the particle-packing recipe at 40 % solid with 15 % allowed overlap, giving 60.0 % porosity.
- Aligned fibres — the fiber-packing recipe turned to lie along z with a 5° orientation spread, at 20 % solid, giving 80.0 % porosity.
Eleven transforms were then applied to one seed of each. Every result, original and transformed, was measured the same way: effective conductivity along x, y and z, porosity, and interfacial area. The solid phase was set to 30 W/m·K, the alumina preset from GET /api/v1/materials.
That is five calls per structure and about 250 calls in total. Every conductivity solve reported meta.converged true and meta.status "solved".
The geometry survives a flip exactly
Flips and rotations returned structures that are geometrically indistinguishable from the original. Porosity came back bit for bit identical.
| Structure | Porosity, original | After four flips and rotations | Interfacial area |
|---|---|---|---|
| Isotropic spheres | 0.599884033203125 | all four identical | agrees to 15 significant figures |
| Aligned fibres | 0.7998161315917969 | all four identical | agrees to 15 significant figures |
The last digit of the interfacial area moves because the sum runs over the voxels in a different order. Nothing else does.
The reduced graph the solver builds is the same size too. The spheres gave 722 nodes and 266 edges before and after every flip and rotation, the fibres 542 and 264.
The transport label does not
Solving those identical structures gave different answers, and on the fibres the difference was large.
| Transform | Spheres, along z | Change | Fibres, along z | Change |
|---|---|---|---|---|
| none | 17.62 W/m·K | — | 14.71 W/m·K | — |
| flip axis = x | 18.82 | +7 % | 19.67 | +34 % |
| flip axis = z | 18.22 | +3 % | 15.98 | +9 % |
| rotate axis = z | 17.29 | −2 % | 14.10 | −4 % |
| three seeds of the recipe | 16.58 to 17.62 | ±3.0 % | 14.07 to 14.71 | ±2.3 % |
Read the last row against the others. Mirroring the fibre structure moved its label fifteen times further than generating a completely different sample of the same material did.
This is not noise in the request. The same stored structure solved three times returned 17.616, 17.657 and 17.615 W/m·K, a spread of 0.04.
Two separate flips of the same original landed on 18.74 and 18.82. They agree with each other far more closely than either agrees with the original.
The cause is visible in the original structure on its own. The spheres have no preferred axis, yet they read 16.82, 15.63 and 17.62 W/m·K along x, y and z. A flip resamples that twelve percent of directional scatter.
A rotation also moves which axis the label belongs to
For an anisotropic structure the label is a direction as well as a number, and rotating the box renames the direction.

Before the rotation, x and y read connectedPath false and z carried everything. After it, x and z read connectedPath false and y carried everything. The bookkeeping is correct, and a copied label has to follow it.
The magnitude did not follow it as cleanly. The conducting direction read 16.48 W/m·K after the rotation and 14.71 before. That is a 12 % move for a structure that was only turned on its side.
If your label is a single scalar taken along a fixed axis, a rotation will silently turn a conducting sample into a non-conducting one. Either rotate the label with the structure, or do not rotate anisotropic structures at all.
Crop and coarsen are new samples, not copies
Cropping and coarsening changed the geometry, so their labels were entitled to change. Porosity says so before any solver is involved.
| Transform | Spheres porosity | Spheres label change | Fibres porosity | Fibres label change |
|---|---|---|---|---|
| none | 60.0 % | — | 80.0 % | — |
| crop 32³, four origins | 58.0 to 60.6 % | −12 to −24 % | 74.6 to 78.3 % | +31 to +83 % |
| coarsen factor 2 | 64.3 % | −11 % | 84.8 % | −24 % |
A 32³ crop of an 80 % porous fibre structure is a small window onto a sparse material. Which fibres you happen to catch decides the answer, and the four origins disagreed by a factor of 1.4 among themselves.
These are legitimate training samples as long as you solve each one. They are not copies of the original, and attaching the original label to them would be the opposite mistake to the one above.
Tiling sits between the two. Doubling the box left porosity identical to the last digit, which is the right behaviour for an intensive quantity. The interfacial area per unit volume fell by about 1 %, because the larger box adds exterior surface.
What to do with each transform
The decision is the same for every transform: find out whether the geometry moved, then either copy the label or compute one.
| Transform | Geometry | Use it as | Label |
|---|---|---|---|
| flip, any axis | identical | a copy | copy the original |
| rotate about z | identical | a copy | copy, x and y swap |
| rotate about x or y | identical | a copy | copy, with the axes renamed |
| tile | intensive quantities identical | a copy | copy; the solver refuses the larger box |
| crop | changed | a new sample | solve it |
| coarsen | changed | a new sample | solve it |
Run the check on your own structure before you trust the table. Anisotropy and sparseness both decide how far a transform will move your label. The fibres moved several times further than the spheres on every row.
POST /api/v1/generate
{ "recipe": { ... , "randomness": { "seed": 101 } },
"storeStructure": true }
-> structureId repeat on three seeds first
POST /api/v1/conductivity
{ "structureId": "<id>",
"request": { "version": "phase0.v1",
"analysisType": "effectiveConductivity",
"inputs": { "grid": { ... }, "materialProperties": { "1": 30 } },
"params": { "direction": "z", "method": "graph-network" },
"requestedOutputs": ["summary"] } }
-> summary.value the label
-> meta.converged must be true
-> meta.connectedPath false means the zero is real
POST /api/v1/flip
{ "structureId": "<id>", "axis": "x" }
-> structureId the transformed copy
POST /api/v1/metrics { "structureId": "<new id>" }
POST /api/v1/surface-area { "structureId": "<new id>",
"materialId": 1, "method": "crofton13" }
-> metrics.porosity and surfaceArea.interfaceAreaUm2
unchanged -> it is a copy, reuse the label
changed -> it is a new sample, solve it
- Measure the seed spread first. Without it there is no threshold to compare a transform against.
- Compare porosity and interfacial area, not just porosity. A crop can land near the original porosity and still be a different structure.
- Keep meta.connectedPath next to every label. A zero with connectedPath false is a real result and a zero without it is not.
- Feed structureId through the chain so that every number comes from the same voxels.
What this does not settle
This is one label from one method. Effective conductivity by graph-network was the only property measured, and the scatter under symmetry is a property of that reduction. Permeability, diffusion and elasticity would each need the same check.
Two structures and three seeds separate the groups. They are not enough to predict how far a flip will move your own label. The fibres moved five times further than the spheres, so the answer depends on the material.
Periodic shifting was in the plan and could not be tested. POST /api/v1/shift returns 404 not_found on the production API, so whether a wrap-around seam disturbs these structures is still open.