PKR Core
Use Case 7 min read

Press the powder harder, and flow pays first

One sphere packing, pressed to six different strains, measured the same way each time. Going from no compaction to 0.40 strain took porosity from 25.9 % to 16.3 %. Tortuosity along z rose by 11 % and effective conductivity fell by 19 %. Permeability fell by 5.9 times.

  • Build Structures
  • Analyze Properties
  • Explore Design Space
Three cross sections through the same particle packing after compaction to strain 0, 0.20 and 0.40, with the top plate position marked.

What you can do

You can run the press itself, then measure what it cost you. One call compacts a generated packing to a target strain and hands back the compacted structure. Everything you would normally measure works on that structure.

  • POST /api/v1/pbd-compaction takes a sphere packing recipe, a target strain and a friction coefficient.
  • It returns per-step metadata and the final voxels, so you can see where the plate ended up.
  • POST /api/v1/metrics gives solid fraction and tortuosity along each axis.
  • POST /api/v1/permeability and POST /api/v1/conductivity give the flow and conduction numbers for the same structure.
  • A compaction call at 48³ came back in 623 to 1,058 ms across the six runs.

That makes the press a parameter you can sweep instead of a process you inherit. The useful output is not one number but the rate at which each number moves.

Why it matters

Pressing a powder is one knob with one setting on it. Calendering an electrode is the usual example, and density is the thing that gets checked on the line. What the same press does to transport is much harder to see.

Modelling the compaction step directly is an active topic, and porosity and tortuosity are increasingly read as a pair. The question that stays open is where to stop. Each point of porosity you press out costs something, and the costs do not arrive at the same rate.

One packing, six press settings

Everything is held fixed except the strain. The recipe packs 320 spheres of 4 µm radius into a 48³ box at 1 µm per voxel, targeting 35 % loading with 10 % overlap, on one seed.

  • Target strains 0, 0.05, 0.10, 0.20, 0.30 and 0.40, with the friction coefficient held at 0.3.
  • The strain 0 point runs through the same compaction path as the others, so it is the baseline everything is compared against.
  • Every run reported ten steps of thirty solver iterations, carrying 320 particles.
Three square cross sections through the same packing after compaction to strain 0, 0.20 and 0.40. Blue is solid particle, mint green is pore. In the first panel solid fills the whole square. In the second a green band of empty space covers the top fifth, with a dashed red line at its lower edge. In the third the green band covers the top two fifths.
The same slice through the box after three press settings. The dashed line is where the top plate stopped. Everything above it is empty grid, not pore.

The grid does not shrink when the plate comes down. At strain 0.40 the plate stopped at z = 28.8 but the returned structure is still 48 voxels tall. The response tells you where the plate is, in the topPlateZ field of the last snapshot.

Two crops before any number is worth reading

Measure the returned structure as it arrives and every number is wrong. Two cuts fix it, and the second one is the surprise.

  • Cut the empty grid above the plate. At strain 0.40 that is 20 of the 48 layers, and leaving it in reports air as pore space.
  • Cut two voxels off each side wall. The packing never touches the walls, so a gap runs the full height of the box around the perimeter.

The wall gap does real damage to one number. With the walls in, tortuosity along z read exactly 1.000 at all six strains. The shortest path and the straight-line distance were identical every time, which means something was open from bottom to top with no sideways step at all.

Two square maps of the box viewed from above, at strain 0 and strain 0.40. Almost the entire square is blue in both. A single mint green line of cells traces the outer edge of each square, marking the columns that are open pore from bottom to top.
Columns that are open pore over the full height, seen from above. At strain 0 there are 95 of them and at strain 0.40 there are 97. They sit on the wall.

Of the 97 open columns at strain 0.40, 96 lie on the outermost ring of voxels. Trim two voxels from each lateral side and no straight column survives at any strain. Tortuosity then reads 1.17 at strain 0 and climbs from there.

Permeability pays first, and pays most

Permeability is the number that pays for the density. Over the six structures porosity fell by 1.6 times. Permeability fell by 5.9 times.

StrainSlab height (voxels)PorosityTortuosity, zPermeability, z (µm²)Conductivity, z (W/m·K)
04825.9 %1.1700.1324.69
0.054524.0 %1.1590.0744.65
0.104323.6 %1.1670.0994.64
0.203820.1 %1.2160.0554.50
0.303318.3 %1.3130.0374.26
0.402816.3 %1.2960.0223.82
Line chart of four quantities against target compressive strain, each divided by its own value at strain 0. Permeability drops steeply to 0.17. Porosity falls steadily to 0.63. Effective conductivity stays near 1.0 until strain 0.20 and ends at 0.81. Tortuosity sits just under 1.0 until strain 0.10, then rises to about 1.11.
The same six structures, four numbers, each divided by its own value at strain 0. Only the spread between the curves matters here.

Tortuosity is flat until strain 0.10, then climbs. It is the only one of the four that goes up. The path through the pore space really does get longer, but slowly.

Effective conductivity is the odd one out. It fell while solid fraction rose from 74.1 % to 83.8 %, which is the opposite of what more solid should do. The reduced graph the solver builds also shrank from 510 nodes to 184 over the sweep, so confirm this curve against a full-grid solve before you lean on it.

The practical reading is about where the press stops being worth it. If flow through the pore space is what you are protecting, the first 0.10 of strain already costs you a quarter of it. If conduction through the solid is what you are buying, this packing did not sell you any.

Friction moved porosity more than another 0.10 of strain

Holding the strain at 0.20 and sweeping friction changed porosity by 3.3 points. Going from strain 0.20 to 0.30 at fixed friction changed it by 1.9 points. The friction coefficient is not a second-order setting here.

Friction coefficientPorosityMax particle overlap (µm)Tortuosity, z
021.7 %8.001.243
0.320.1 %8.001.216
0.619.2 %8.001.216
1.018.4 %7.651.270

The plate stopped at the same height in all four runs, so the box volume was identical. What changed is how far the particles pushed into each other. The friction 1.0 run is the only one whose maximum pairwise overlap stayed under 8 µm, and less interpenetration leaves more of the box counted as solid.

Running it on your own recipe

Send a sphere packing recipe and a strain, read topPlateZ off the last snapshot, crop, then measure. The recipe here started from the particle-packing entry in GET /api/v1/examples, with the grid, seed, loading and overlap overwritten.

POST /api/v1/pbd-compaction
{
  "recipe": {
    "generatorType": "particlePacking",
    "grid": { "nx": 48, "ny": 48, "nz": 48, "voxelSizeUm": 1 },
    "geometryParams": {
      "fillers":   [ { "type": "sphere", "radius": 4, "volumeFraction": 35, "materialId": 1 } ],
      "particles": [ { "type": "sphere", "radius": 4, "volumeFraction": 35, "materialId": 1 } ]
    },
    "volumeFraction": 35,
    "overlapPercent": 10,
    "seed": 20260927
  },
  "targetStrain": 0.40,
  "frictionCoefficient": 0.3
}
-> snapshots[last].topPlateZ = 28.8
-> finalStructure.grid.nz    = 48      // unchanged; crop to z < 28

POST /api/v1/structures       -> structureId, for the cropped structure
POST /api/v1/metrics          { "structureId": "<id>" }
-> metrics.tortuosity.z.ratio

The loading has to be written into several places at once. The generator reads geometryParams, fillerConfigs and distributionRules, so setting only the top-level volumeFraction leaves the packing at whatever the example shipped with.

Keep the grid modest. The response carries finalStructure.voxels as a plain array, so 64³ and above runs into the body size limit. At 48³ the whole first pass came to 31 requests and 13 seconds of API time.

Include strain 0 in the sweep. It goes through the same compaction and voxelisation path as every other point, which a structure from POST /api/v1/generate does not. Comparing against the wrong baseline is the easiest way to invent a trend.

What this does not settle

Each point is a shorter slab than the one before it, from 48 layers down to 28. That is what pressing does, but it means the six measurements are not a fixed window onto a changing material.

The permeability curve wobbles at strain 0.10. The top face is a cut surface, and the open pore area it exposes went 994, 613, 822, 545, 412, 284 voxels across the six strains. Permeability tracks that wobble, so where the crop plane lands is part of the answer.

This is one seed, one particle size and one recipe. The ordering of the four curves is the result worth carrying over. The individual numbers are this packing only.

Try it in PKR Core.

Open PKR Core