Generating aggregates
All generation algorithms share one entry point, the factory function
pfa.generate():
import pyFracAggregate as pfa
agg = pfa.generate(n_particles=64, df=1.8, kf=1.9)
generate() selects an algorithm on three orthogonal axes — method
(the aggregation schedule), scaling (how the parallel-axis target distances
are weighted), and placement (how contacts are found) — so every classical
algorithm from the literature is one coordinate in this system.
Parameters
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
Target number of primary particles. |
|
|
— |
Fractal dimension \(D_f\). |
|
|
— |
Fractal prefactor \(k_f\). |
|
|
|
Algorithm family: |
|
|
|
Parallel-axis weighting: |
|
|
|
Contact strategy: |
|
|
|
Primary-particle radius distribution; |
|
|
|
Maximum allowed interpenetration between sphere surfaces (in length units). |
|
|
|
Seed for bit-reproducible generation; |
For soot-like systems, typical values are \(D_f \approx 1.6\)–1.9 and
\(k_f \approx 1.2\)–2.4; the science behind both parameters is in
Morphology parameters.
Keyword-only unit arguments are forwarded to the generator:
length_unit='nm', mass_unit='g', and density=1.0 (see
Units).
The coordinate system
Literature method |
Coordinate |
|---|---|
DLA-style PCA |
|
Filippov CCA (2000) |
|
FLAGE-style CCA (Skorupski 2014) |
|
FracVAL (Morán 2019) |
|
agg_dla = pfa.generate(32, 1.78, 1.9, method="pca", scaling="count", placement="solved")
agg_filippov = pfa.generate(32, 1.78, 1.9, method="cca", scaling="count", placement="sampled")
agg_flage = pfa.generate(32, 1.78, 1.9, method="cca", scaling="count", placement="solved")
agg_fracval = pfa.generate(32, 1.78, 1.9, method="cca", scaling="mass", placement="constructed")
As a rule of thumb: pca for fast scans, (cca, mass, constructed) for
polydisperse soot models (the FracVAL coordinate), (cca, count, sampled)
to reproduce Filippov-style generation. The trade-offs are compared in
Choosing a method.
Note
method='fracval' is a deprecated alias for
(cca, mass, constructed) — it emits a DeprecationWarning and will be
removed in 1.0. method='tdcca' (Thouy & Jullien) was removed in v0.4
without a replacement; pin pyFracAggregate<0.4 if you need it.
The scaling axis
The parallel-axis theorem splits every cluster by weights: counting each
primary as one (scaling='count', the Filippov 2000 original) or weighting
by mass (scaling='mass', physically correct for polydisperse primaries,
following Morán 2019). Both have scientific semantics — count reproduces the
original papers, mass handles size distributions honestly — so the axis is
exposed rather than hard-wired.
For a monodisperse distribution the two are mathematically equivalent
(masses are proportional to counts), so the 'mass' default changes nothing
for monodisperse users. For polydisperse input, 'mass' is the default
since v0.4 — this is a behavior change from v0.3, where cca used count
weighting ('fracval' was the only mass-weighted path).
Particle size distributions
Primary-particle radii come from a ParticleDistribution. Two are provided:
Monodisperse(radius)— all primaries share one radius.LognormalDistribution(mean, std)—meanis the geometric mean radius andstdthe geometric standard deviation (values at or below 1.0 collapse to monodisperse).
import numpy as np
mono = pfa.Monodisperse(radius=15.0)
agg_mono = pfa.generate(32, 1.8, 1.9, particle_dist=mono)
print(np.unique(agg_mono.radii)) # [15.]
poly = pfa.LognormalDistribution(mean=15.0, std=1.6)
agg_poly = pfa.generate(32, 1.8, 1.9, particle_dist=poly)
print(agg_poly.radii.min(), agg_poly.radii.max()) # e.g. 4.67 ... 36.68
For the physics of polydisperse primaries see Primary particles.
Overlap tolerance
overlap_tolerance is the maximum allowed overlap between two sphere
surfaces: a contact is valid when
\(d_{ij} \geq r_i + r_j - \delta\) with \(\delta\) the tolerance. The
default of 1e-5 (in length units) produces essentially hard-sphere
contacts. Larger values let primaries interpenetrate — occasionally wanted to
mimic sintering necks, at the cost of biasing the realized morphology and any
fractal dimension measured from it:
sintered = pfa.generate(32, 1.8, 1.9, overlap_tolerance=0.3)
Placement strategy
The placement argument selects how each new particle or subcluster is
brought into contact with the existing aggregate. The names describe how the
contact comes to be: it is solved in closed form, sampled by Monte
Carlo, or constructed from a specified contact pair.
'solved'(default) — FLAGE-style closed-form tangency computation with a Monte Carlo fallback (Skorupski et al., 2014). Contacts are near-exact and target distances are honored precisely.'sampled'— pure Monte Carlo sampling with gradual tolerance relaxation (Filippov et al., 2000). Contacts are slightly fuzzier, and generation is typically several times slower at moderate \(N\).'constructed'— FracVAL contact construction (Morán et al., 2019): a specified contact pair is selected and the merging cluster is rotated and translated into place, with a center-of-mass check. Cluster merging only —method='pca'withplacement='constructed'raisesValueError.
agg = pfa.generate(32, 1.8, 1.9, placement="sampled")
Practical guidance: keep the default — it is both more precise and faster.
Switch to 'sampled' to reproduce older Filippov-style generation, and to
'constructed' for the FracVAL coordinate. The deprecated names
'algebraic' (→ 'solved') and 'random' (→ 'sampled') still resolve
with a warning and will be removed in 1.0.
Working with the Aggregate
generate() returns an Aggregate: a
pre-allocated (max_particles, 5) NumPy array storing
[x, y, z, radius, mass]. The array-like accessors return zero-copy
views — mutating them mutates the aggregate:
agg = pfa.generate(64, 1.8, 1.9)
agg.positions # view, shape (N, 3)
agg.radii # view, shape (N,)
agg.masses # view, shape (N,)
agg.current_size # number of valid particles (int) — the "N" of the cluster
agg.to_numpy() # copy of the (N, 5) data block
Note the accessor for the particle count is current_size (the analysis
helper pfa.analyze returns it as report.n).
Factory function or generator classes
pfa.generate() is a thin factory over two generator classes with an
identical constructor shape —
PCAGenerator, CCAGenerator.
Instantiating a class directly is equivalent but makes the full constructor
surface explicit, including the unit parameters that generate() only
forwards as keywords. The scaling and placement arguments also accept
instances (CountScaling()/MassScaling(), any PlacementStrategy)
pandas-style, for custom strategies:
from pyFracAggregate.core.scaling import MassScaling
gen = pfa.CCAGenerator(
n_particles=32, df=1.8, kf=1.9,
particle_dist=pfa.Monodisperse(1.0),
scaling=MassScaling(1.8, 1.9),
length_unit="um", mass_unit="kg", density=1.8,
seed=42,
)
agg = gen.generate()
Reproducibility and seeding
Generation is stochastic. All generators draw randomness from a private
NumPy Generator created from the seed argument — generation never
consults the global numpy.random state, so numpy.random.seed(...) has
no effect on generation. (Calling distribution.sample(n) or
random_point_on_circle() directly, without a generator, still uses the
global stream.)
first = pfa.generate(32, 1.8, 1.9, seed=42)
second = pfa.generate(32, 1.8, 1.9, seed=42)
print(np.array_equal(first.positions, second.positions)) # True
Same seed → bit-identical particle data (positions, radii, masses) for any
legal coordinate; different seed → a different realization; seed=None →
fresh entropy every call. Note that for pca the radius of gyration is
essentially fixed by (n_particles, df, kf) — each particle sits at exactly
the target distance from the running center, so the seed varies the
cluster’s texture (orientations and contact choices), not its overall size.