Coagulation Strategy System¶
Strategy-based coagulation kernels with unified builders, factory selection, runnable integration, and particle-resolved support.
Overview¶
The coagulation strategy system lets you model particle collisions using the same object-oriented patterns as condensation and wall loss. Instead of calling standalone kernel functions, you work with strategy objects that operate on ParticleRepresentation, support multiple distribution types, and expose a consistent kernel / rate / step interface. Strategies cover Brownian motion, electrostatic enhancement, turbulence (shear and DNS), gravitational sedimentation, and user-defined combinations.
This feature is built around user-facing APIs exposed via particula.dynamics:
CoagulationStrategyABC– abstract base class defining kernel, gain/loss/net rate, andstepfor discrete, continuous, or particle-resolved distributions.BrownianCoagulationStrategy,ChargedCoagulationStrategy,TurbulentShearCoagulationStrategy,TurbulentDNSCoagulationStrategy,SedimentationCoagulationStrategy– concrete physical models.CombineCoagulationStrategy– sums multiple kernels of the same distribution type for combined physics.- Builders for each strategy plus mixins for distribution type, turbulent dissipation, fluid density, and charged-kernel validation.
CoagulationFactory– factory for selecting a coagulation strategy by name with builder defaults.Coagulationrunnable – delegates to a coagulation strategy, splitstime_stepacrosssub_steps, and composes with other runnables using the|operator.
Key Benefits¶
- Consistent dynamics workflow: Same strategy-based API (
kernel,rate,step,distribution_type) and runnable integration as condensation and wall loss. - Builder/factory parity with validation: Fluent builders enforce distribution type, turbulent parameters, fluid density, and charged kernel strategy selection with optional unit conversion.
- Coverage of major coagulation regimes: Brownian, charged/electrostatic, turbulent shear, turbulent DNS, and sedimentation kernels plus additive combinations.
- Distribution-type flexibility: Works with "discrete", "continuous_pdf", and "particle_resolved" modes so you can reuse existing particle builders.
- Pipeline ready:
Coagulationrunnable usessub_steps, clamps through strategy logic, and chains with other runnables via|for end-to-end simulations.
Who It's For¶
This feature is designed for:
- Chamber and box-model users: Running time-dependent simulations where coagulation competes with condensation, wall loss, dilution, or emissions.
- Particle-resolved modelers: Requiring discrete collision pairs, kernel radius binning, and stochastic pair selection.
- Turbulence and charged-aerosol researchers: Exploring electrostatic enhancement or turbulence-driven collision rates without re-implementing kernels.
- Model developers: Extending with new kernels while reusing validation, distribution handling, and runnable plumbing.
Capabilities¶
Unified coagulation API in particula.dynamics¶
Coagulation is exposed alongside other dynamics components:
import particula as par
# Abstract interface
par.dynamics.CoagulationStrategyABC
# Concrete strategies
par.dynamics.BrownianCoagulationStrategy
par.dynamics.ChargedCoagulationStrategy
par.dynamics.TurbulentShearCoagulationStrategy
par.dynamics.TurbulentDNSCoagulationStrategy
par.dynamics.SedimentationCoagulationStrategy
# Combination and factory
par.dynamics.CombineCoagulationStrategy
par.dynamics.CoagulationFactory
All strategies share a common shape:
- Initialize with physical parameters (e.g., turbulent dissipation, fluid density, charged kernel strategy) and
distribution_type. - Call
kernel(particle, temperature, pressure)to compute the dimensional kernel [m^3/s]. - Call
net_rate(...),loss_rate(...), orgain_rate(...)as needed. - Call
step(particle, temperature, pressure, time_step)to advance the system.
Runnable entry point: Coagulation¶
Coagulation is a RunnableABC exported as par.dynamics.Coagulation. It operates on an Aerosol, delegates step to the provided coagulation strategy, splits time_step across any sub_steps, and composes with other runnables using |:
import particula as par
aerosol = ... # your Aerosol instance
coagulation = par.dynamics.Coagulation(
coagulation_strategy=par.dynamics.BrownianCoagulationStrategy(
distribution_type="discrete",
),
)
# Sub-steps split time_step internally
updated = coagulation.execute(
aerosol,
time_step=60.0,
sub_steps=4,
)
# Chain with wall loss or condensation in one pipeline
wall_loss = par.dynamics.WallLoss(
wall_loss_strategy=par.dynamics.SphericalWallLossStrategy(
wall_eddy_diffusivity=1e-3,
chamber_radius=0.5,
distribution_type="discrete",
),
)
pipeline = coagulation | wall_loss
updated = pipeline.execute(aerosol, time_step=60.0)
Brownian coagulation strategy¶
BrownianCoagulationStrategy implements the classical Brownian kernel using get_brownian_kernel_via_system_state. It works across discrete, continuous, and particle-resolved distributions.
Typical initialization:
brownian = par.dynamics.BrownianCoagulationStrategy(
distribution_type="discrete",
)
Charged coagulation with kernel strategies¶
ChargedCoagulationStrategy wraps electrostatic kernel strategies (e.g., HardSphereKernelStrategy, Coulomb* variants). Provide a ChargedKernelStrategyABC instance:
charged_kernel = par.dynamics.HardSphereKernelStrategy()
charged = par.dynamics.ChargedCoagulationStrategy(
distribution_type="discrete",
kernel_strategy=charged_kernel,
)
Use the builder for validation and readability:
charged = (
par.dynamics.ChargedCoagulationBuilder()
.set_distribution_type("discrete")
.set_charged_kernel_strategy(par.dynamics.HardSphereKernelStrategy())
.build()
)
Turbulent shear coagulation¶
TurbulentShearCoagulationStrategy uses the Saffman–Turner (1956) shear kernel. Requires turbulent dissipation [m2/s3] and fluid density [kg/m^3]:
turb_shear = par.dynamics.TurbulentShearCoagulationStrategy(
distribution_type="continuous_pdf",
turbulent_dissipation=1e-3,
fluid_density=1.225,
)
Builder example with unit conversion and validation:
turb_shear = (
par.dynamics.TurbulentShearCoagulationBuilder()
.set_distribution_type("continuous_pdf")
.set_turbulent_dissipation(1e-3, "m^2/s^3")
.set_fluid_density(1.225, "kg/m^3")
.build()
)
Turbulent DNS coagulation¶
TurbulentDNSCoagulationStrategy follows Ayala et al. (2008) DNS fits. Requires turbulent dissipation, fluid density, Reynolds lambda (dimensionless), and relative velocity [m/s]:
turb_dns = par.dynamics.TurbulentDNSCoagulationStrategy(
distribution_type="discrete",
turbulent_dissipation=0.01,
fluid_density=1.225,
reynolds_lambda=74.0,
relative_velocity=0.5,
)
Factory configuration dict example (via CoagulationFactory):
factory = par.dynamics.CoagulationFactory()
turb_dns = factory.get_strategy(
strategy_type="turbulent_dns",
parameters={
"distribution_type": "discrete",
"turbulent_dissipation": 1e-2, # m^2/s^3
"fluid_density": 1.225, # kg/m^3
"reynolds_lambda": 150.0, # dimensionless
"relative_velocity": 0.6, # m/s
},
)
Sedimentation coagulation¶
SedimentationCoagulationStrategy models gravitational-settling-driven collisions (Seinfeld & Pandis, 2016) and works across supported distribution types. No extra parameters beyond distribution_type are required.
Combined coagulation strategies¶
CombineCoagulationStrategy sums kernels from multiple strategies (distribution type must match). Example: Brownian + turbulent shear:
combined = par.dynamics.CombineCoagulationStrategy(
strategies=[
par.dynamics.BrownianCoagulationStrategy(distribution_type="discrete"),
par.dynamics.TurbulentShearCoagulationStrategy(
distribution_type="discrete",
turbulent_dissipation=1e-3,
fluid_density=1.225,
),
]
)
Support for multiple distribution types¶
The strategy system operates on the same distribution types used elsewhere in particula:
- "discrete" – radius-binned distributions.
- "continuous_pdf" – continuous probability-density representations.
- "particle_resolved" – ensembles of individual particles with stochastic pair selection.
Select the appropriate mode at initialization (or via builders/factory). Particle-resolved paths use kernel-radius binning and collide_pairs updates; discrete/continuous paths apply gain/loss rate updates.
Particle-resolved coagulation¶
CoagulationStrategyABC.step handles particle-resolved updates by mapping to a kernel radius grid, drawing collision pairs, and updating via collide_pairs:
particle_resolved = par.dynamics.BrownianCoagulationStrategy(
distribution_type="particle_resolved",
)
aerosol.particles = particle_resolved.step(
particle=aerosol.particles,
temperature=298.15,
pressure=101325.0,
time_step=1.0,
)
Builder and factory workflow¶
Builders provide validated, unit-aware construction; the factory selects a strategy by name using those builders under the hood:
import particula as par
# Brownian via builder
brownian = (
par.dynamics.BrownianCoagulationBuilder()
.set_distribution_type("discrete")
.build()
)
# Charged via builder with kernel strategy
charged = (
par.dynamics.ChargedCoagulationBuilder()
.set_distribution_type("continuous_pdf")
.set_charged_kernel_strategy(par.dynamics.HardSphereKernelStrategy())
.build()
)
# Factory selection (combine)
factory = par.dynamics.CoagulationFactory()
combined = factory.get_strategy(
strategy_type="combine",
parameters={
"strategies": [brownian, charged],
"distribution_type": "continuous_pdf",
},
)
Getting Started¶
Quick start: Brownian coagulation on a discrete distribution¶
import particula as par
# 1. Build a radius-binned particle distribution
particle = par.particles.PresetParticleRadiusBuilder().build()
# 2. Configure Brownian coagulation
brownian = par.dynamics.BrownianCoagulationStrategy(
distribution_type="discrete",
)
# 3. Compute instantaneous kernel and rates
T = 298.15 # K
P = 101325.0 # Pa
kernel = brownian.kernel(particle, temperature=T, pressure=P)
net = brownian.net_rate(particle, temperature=T, pressure=P)
# 4. Advance by one time step
particle = brownian.step(
particle=particle,
temperature=T,
pressure=P,
time_step=10.0, # s
)
Prerequisites¶
particulaversion 0.2.6 or later installed.- A
ParticleRepresentationinstance (e.g., from preset particle builders). - Basic familiarity with particula dynamics and the runnable pipeline (
|).
Typical Workflows¶
1. Build a ParticleRepresentation¶
Use preset builders or custom particle-resolved constructors:
particle = (
par.particles.PresetParticleRadiusBuilder()
.set_volume(1.0, "m^3")
.build()
)
2. Configure a coagulation strategy¶
Pick a physical model and distribution type:
coag_strategy = par.dynamics.TurbulentShearCoagulationStrategy(
distribution_type="discrete",
turbulent_dissipation=5e-4,
fluid_density=1.2,
)
3. Run through the Coagulation runnable (with sub_steps)¶
coagulation = par.dynamics.Coagulation(coagulation_strategy=coag_strategy)
updated = coagulation.execute(
aerosol,
time_step=120.0,
sub_steps=3,
)
4. Compose with other dynamics in a single pipeline¶
Order processes explicitly using the | operator:
condensation = par.dynamics.MassCondensation(
condensation_strategy=par.dynamics.CondensationIsothermal(
molar_mass=0.18,
diffusion_coefficient=2e-5,
accommodation_coefficient=1.0,
),
)
wall_loss = par.dynamics.WallLoss(
wall_loss_strategy=par.dynamics.SphericalWallLossStrategy(
wall_eddy_diffusivity=1e-3,
chamber_radius=0.5,
distribution_type="discrete",
),
)
pipeline = condensation | coagulation | wall_loss
updated = pipeline.execute(aerosol, time_step=60.0, sub_steps=2)
5. Builder/factory workflow for reproducibility¶
factory = par.dynamics.CoagulationFactory()
coag_strategy = factory.get_strategy(
strategy_type="brownian",
parameters={"distribution_type": "continuous_pdf"},
)
coagulation = par.dynamics.Coagulation(coagulation_strategy=coag_strategy)
6. Particle-resolved collision loop¶
particle_resolved = par.dynamics.BrownianCoagulationStrategy(
distribution_type="particle_resolved",
)
aerosol.particles = particle_resolved.step(
particle=aerosol.particles,
temperature=300.0,
pressure=101325.0,
time_step=0.5,
)
Use Cases¶
Use case 1: Brownian-only chamber decay¶
Scenario: You need size-dependent Brownian coagulation over a 10-minute chamber experiment.
Solution: Use BrownianCoagulationStrategy with Coagulation runnable and sub_steps to maintain stability at small time_step slices.
Use case 2: Charged aerosol aging¶
Scenario: You are studying charge-enhanced coagulation (e.g., ion-induced charging or corona discharge) and want to compare kernel strategies.
Solution: Instantiate ChargedCoagulationStrategy with HardSphereKernelStrategy (or other Coulomb models) and sweep voltages/charges externally while keeping the same runnable pipeline.
Use case 3: Turbulent industrial flow¶
Scenario: You want turbulence-driven coagulation rates for an industrial duct with known dissipation and Reynolds lambda.
Solution: Configure TurbulentDNSCoagulationStrategy via CoagulationFactory using measured turbulent_dissipation, fluid_density, reynolds_lambda, and relative_velocity; run through Coagulation with other dynamics chained.
Configuration¶
| Option | Description | Default / Units | Applies To |
|---|---|---|---|
distribution_type |
"discrete", "continuous_pdf", or "particle_resolved" | Required | All strategies & builders |
charged_kernel_strategy |
Electrostatic kernel (e.g., HardSphereKernelStrategy) |
Required | Charged |
turbulent_dissipation |
Turbulent energy dissipation rate | Required, m2/s3 | Turbulent shear, Turbulent DNS |
fluid_density |
Fluid density for the medium | Required, kg/m^3 | Turbulent shear, Turbulent DNS |
reynolds_lambda |
Taylor-scale Reynolds number | Required | Turbulent DNS |
relative_velocity |
Relative particle-flow velocity | Required, m/s | Turbulent DNS |
strategies |
List of strategies to sum | Required | Combine |
particle_resolved_kernel_radius |
Kernel-radius grid (optional) | Optional | Particle-resolved modes |
particle_resolved_kernel_bins_number |
Number of kernel bins | Optional | Particle-resolved modes |
particle_resolved_kernel_bins_per_decade |
Bins per radius decade | 10 |
Particle-resolved modes |
Best Practices¶
- Match distribution type to particle builder: Keep
distribution_typeconsistent with howParticleRepresentationwas constructed to avoid unintended kernel application. - Use builders/factory for validation: Let builders enforce turbulence units, fluid density, and charged-kernel selection; avoid manual instantiation when sharing configurations.
- Start with smaller
time_stepand usesub_steps: Especially for particle-resolved or highly turbulent cases to reduce overshoot in collisions. - Combine kernels deliberately: Use
CombineCoagulationStrategywhen you need additive physics (e.g., Brownian + turbulent shear); ensure all strategies share the same distribution type. - Keep parameters physical: Validate dissipation rates, Reynolds lambda, and relative velocities against your experiment or CFD results before running long simulations.
Limitations¶
- Dimensionless kernels are not implemented for combined, turbulent, or sedimentation strategies; use dimensional forms via
kernel. - All strategies in
CombineCoagulationStrategymust share the samedistribution_type. - No built-in high-level orchestrator; you control the time loop and pipeline ordering.
- DNS parameterization assumes applicability of Ayala et al. (2008) fits; verify for extreme regimes.
Related Documentation¶
- Testing guide: adw-docs/testing_guide.md
- Code style: adw-docs/code_style.md
- Architecture reference: adw-docs/architecture_reference.md
- Wall loss strategies: wall_loss_strategy_system.md
- Condensation strategies: condensation_strategy_system.md
FAQ¶
How do I choose between turbulent shear and turbulent DNS?¶
Use turbulent shear (TurbulentShearCoagulationStrategy) for Saffman–Turner regimes with moderate dissipation and when Reynolds lambda is not specified. Use turbulent DNS (TurbulentDNSCoagulationStrategy) when you have DNS-fit parameters (reynolds_lambda, relative_velocity) or want higher-Re effects.
Can I mix charged and turbulent effects?¶
Yes. Build charged and turbulent strategies with the same distribution_type, then sum them via CombineCoagulationStrategy (or factory strategy_type="combine") to add their kernels.
How does particle-resolved stepping differ from discrete bins?¶
Particle-resolved mode bins a kernel radius grid, draws collision pairs stochastically, and calls collide_pairs; discrete/continuous modes apply gain/loss rates directly to concentrations.