What you can do
You can stack measured structures into one structure and measure that. One call joins two stored structures along an axis and hands back a new structure you can send to any analysis.
- POST /api/v1/compose takes two or more structureIds, a mode and an axis, and returns a composed structureId.
- The axes you are not stacking along have to match, so a 64x64x32 layer stacks onto another 64x64x32 layer and a mismatch comes back as a 400.
- Set remapMaterials to preserve and the material ids you measured the layers with still mean the same thing.
- POST /api/v1/metrics, /api/v1/diffusion and /api/v1/permeability all accept the composed structureId directly.
That gives you a cheap test you can run on your own recipe. Measure each layer, predict the stack, then measure the stack and see how far apart they are.
Why it matters
Grading porosity through the thickness is a live design idea for gas diffusion layers and porous transport layers. Whether it helps depends on numbers for the graded stack, not for any one layer.
What you usually have is the layer numbers. Each layer was made and measured on its own, and combining them with a mixing rule is the obvious shortcut. The question is whether that shortcut survives contact with your structure.
It survived for one of the two properties measured here and not the other. Which means the answer is a property of your recipe, and worth ten minutes of checking before you lean on it.
Three structures, one mean porosity
The comparison only works if the stacked and uniform structures hold the same amount of pore space. They did, to better than 0.2 points on every seed.
- Layer A is a 64x64x32 sphere packing at 45 % target loading, the denser layer.
- Layer B is the same recipe at 25 % target loading, the coarser layer.
- The stack is layer A and layer B joined along z, giving a 64x64x64 box.
- The uniform structure is one 64x64x64 packing at 35 % loading, built to match the stack on mean porosity.
- Everything else is held fixed: the same particle-packing example, 15 % overlap and one seed per replicate.
| Structure | Grid | Porosity, mean of 5 seeds | Porosity, spread |
|---|---|---|---|
| Layer A alone | 64x64x32 | 54.93 % | 54.82 to 54.99 % |
| Layer B alone | 64x64x32 | 74.92 % | 74.83 to 74.99 % |
| Stacked A + B | 64x64x64 | 64.92 % | 64.82 to 64.98 % |
| Uniform | 64x64x64 | 64.97 % | 64.92 to 65.00 % |

The whole experiment was then repeated on five seeds. One replicate is five structures, one compose call and twelve analyses, which came to 16 requests and about 7 seconds of API time.
For diffusion, the layer numbers add up
The series rule predicted the stacked diffusivity to within 2.9 % on every seed. That rule combines the two layers as resistances in series, weighted by thickness, which here is half and half.
| Quantity, along z | Mean of 5 seeds | Gap from the measured stack |
|---|---|---|
| Layer A alone | 0.825 | |
| Layer B alone | 0.910 | |
| Series rule prediction | 0.865 | +0.8 to +2.9 % |
| Stacked, measured | 0.881 | |
| Uniform, measured | 0.889 | −0.1 to −2.0 % |
Effective diffusion is reported relative to the free diffusivity of the pore phase, so 0.881 means the stack passes 88 % of what open space would. The prediction ran slightly low on all five seeds, which is a bias rather than noise.
A bias that small is usually not worth chasing. If you can tolerate a 3 % error on diffusivity, you can compose your layer measurements and skip building the stack.
For permeability, the average hides the problem
The same rule applied to permeability missed by −11 % on one seed and +16 % on another. There is no correction to apply, because the error does not keep its sign.
Averaging the five seeds makes it look fine, and that is the trap. The predicted permeability averages 0.363 µm² and the measured stack averages 0.353 µm², which is under 3 % apart.
| Permeability along z, µm² | Mean of 5 seeds | Lowest seed | Highest seed |
|---|---|---|---|
| Layer A alone | 0.273 | 0.234 | 0.309 |
| Layer B alone | 0.543 | 0.505 | 0.559 |
| Series rule prediction | 0.363 | 0.320 | 0.398 |
| Stacked, measured | 0.353 | 0.320 | 0.373 |
| Uniform, measured | 0.400 | 0.382 | 0.435 |
A single run gives you one seed, not the average of five. On this recipe that one run would have told you the rule works beautifully or fails badly, with no way to tell which from the run itself.
Same pore space, less permeable
The stacked structure was less permeable than the uniform one on all five seeds, by 4.3 % to 21.4 %. Both hold the same mean porosity to within 0.2 points, so porosity is not what separates them.
Diffusion barely noticed the same rearrangement. Stacked came in 0.1 % to 2.0 % below uniform, close enough to the run-to-run scatter that the gap is hard to call.
The reading is that the dense layer sets the flow and the coarse layer cannot make up for it. Transport tortuosity was 1.21 in layer A against 1.10 in layer B, so the two layers are not far apart on path length. The cost is in the narrow throats rather than in a longer route.
For a graded design that matters directly. Quoting a target mean porosity does not pin down the permeability you will get, and the grading itself costs you something on this recipe.
Measuring across the layers instead of through them
Measuring along x puts the two layers side by side rather than end to end, so the parallel rule should apply instead of the series rule. For diffusion this direction cannot settle anything.
The two rules only disagree by 0.2 to 0.3 % for these layers, because layer A and layer B are close on diffusivity to begin with. Any measurement lands between them.
For permeability the two rules are 9 % to 15 % apart, so the test is real, and the measurement matched neither. The parallel prediction was off by +0.1 % to −11.8 % across the five seeds, which is the same scattered picture as before.
The stacked structure was more permeable across the layers than through them on all five seeds, by 3 % to 25 %. The uniform structure straddled 1.0 on the same comparison, so some of that spread is the measurement rather than the geometry.
Running it on your own recipe
Generate each layer with storeStructure set, compose the ids, then send the composed id to the same analyses you ran on the layers.
POST /api/v1/generate { "recipe": { ...45 % loading, nz 32 }, "storeStructure": true }
-> structureId: "A"
POST /api/v1/generate { "recipe": { ...25 % loading, nz 32 }, "storeStructure": true }
-> structureId: "B"
POST /api/v1/compose
{
"structures": [ { "structureId": "A" }, { "structureId": "B" } ],
"mode": "stack",
"axis": "z",
"remapMaterials": "preserve"
}
-> structureId, structureSummary.grid = { nx: 64, ny: 64, nz: 64 }
POST /api/v1/diffusion
{
"structureId": "<composed>",
"request": {
"version": "phase0.v1",
"analysisType": "effectiveDiffusion",
"inputs": { "grid": { "nx": 64, "ny": 64, "nz": 64 },
"materialProperties": { "0": 1 } },
"params": { "direction": "z", "method": "graph-network", "poreMaterialIds": [0] },
"requestedOutputs": [ "summary" ]
}
}
-> summary.value, transportTortuosity.value
- Put the diffusivity on material 0, the pore phase. Put it on the solid instead and summary.value is unchanged, but transportTortuosity comes back unavailable with the reason "bulk diffusivity is not positive".
- Keep remapMaterials on preserve. The default reassigns ids per input, so two layers whose solid was material 1 come back as materials 1 and 2. Every later material reference then points at the wrong phase.
- Match the grid on the axes you are not stacking. A 12-wide layer stacked onto a 16-wide one is refused with "Structure 2 has nx=12; expected 16 for z-axis stacking".
- Check the composed porosity against your uniform reference before comparing anything. If they are more than a point apart, the comparison is about porosity and not about layering.
- Run at least three seeds. One seed cannot tell you whether a gap is the geometry or the run.
The loading has to be written into several places in the recipe at once. The generator reads geometryParams, fillerConfigs and distributionRules, so setting only the top-level volumeFraction leaves the packing where the example had it.
What this does not settle
This is two layers at half and half, on one example recipe, in a 64³ box. A thinner dense layer or a sharper porosity contrast could move both results, and the series rule was only ever tested at one thickness ratio.
Both solvers work on a reduced network rather than the full grid. The permeability scatter may be partly the network extraction rather than the structure, and a full-grid solve is the way to separate those.
The join itself is abrupt. Real graded layers blend over some distance, and nothing here says whether a blended join behaves like this one.