-
Notifications
You must be signed in to change notification settings - Fork 10
Description
Dependencies:
#1068 [E3-F4-P1] ──┐
├──► #THIS [E3-F4-P4]
#1069 [E3-F4-P2] ──┘
Parent Issue: #1020
Dependencies:
- E3-F4-P1: [Phase E3-F4-P1] Create ParticleRepresentation facade over ParticleData with deprecation warnings and tests #1068
- E3-F4-P2: [Phase E3-F4-P2] Create GasSpecies facade over GasData with deprecation warnings and tests #1069
Description
Update CoagulationStrategyABC and its concrete implementations (BrownianCoagulationStrategy, ChargedCoagulationStrategy, SedimentationCoagulationStrategy, TurbulentShearCoagulationStrategy, TurbulentDnsCoagulationStrategy, CombineCoagulationStrategy) to accept both legacy ParticleRepresentation and new ParticleData types. When ParticleData is passed, the strategy should work with it directly; when ParticleRepresentation is passed, behavior remains unchanged.
Context
This is the fourth phase of E3-F4. With the facades in place (P1 #1068, P2 #1069), the coagulation strategies can now accept both old and new types. The coagulation ABC (coagulation_strategy_abc.py, 477 lines) defines kernel(), loss_rate(), gain_rate(), net_rate(), step(), diffusive_knudsen(), coulomb_potential_ratio(), and friction_factor() - all taking ParticleRepresentation. The concrete strategies override kernel() and dimensionless_kernel().
Parent Issue: #1020 (E3-F4: Facade and Migration)
Depends on: #1068 (P1: ParticleRepresentation facade), #1069 (P2: GasSpecies facade)
Value:
- Enables users to pass
ParticleDatadirectly to coagulation without wrapping in facades - Reduces overhead for new code paths that don't need strategy coupling
- Consistent dual-type support across all dynamics modules
Scope
Estimated Lines of Code: ~150 lines (excluding tests)
Complexity: Medium
Files to Modify:
particula/dynamics/coagulation/coagulation_strategy/coagulation_strategy_abc.py(~100 LOC changes) - ABC with shared methodsparticula/dynamics/coagulation/coagulation_strategy/brownian_coagulation_strategy.py(~10 LOC) - Updatekernel()signatureparticula/dynamics/coagulation/coagulation_strategy/charged_coagulation_strategy.py(~10 LOC)particula/dynamics/coagulation/coagulation_strategy/sedimentation_coagulation_strategy.py(~10 LOC)particula/dynamics/coagulation/coagulation_strategy/turbulent_shear_coagulation_strategy.py(~10 LOC)particula/dynamics/coagulation/coagulation_strategy/turbulent_dns_coagulation_strategy.py(~10 LOC)
Files to Modify (tests):
particula/dynamics/coagulation/coagulation_strategy/tests/brownian_coagulation_strategy_test.py(~50 LOC additions)
Acceptance Criteria
Core Implementation
- Add
_unwrap_particle()helper tocoagulation_strategy_abc.py(or reuse from a shared utility if condensation P3 extracts it) - Update
CoagulationStrategyABC.kernel()abstract signature toUnion[ParticleRepresentation, ParticleData] - Update
CoagulationStrategyABC.loss_rate()to accept both types - extractconcentrationandget_radius()equivalently from both - Update
CoagulationStrategyABC.gain_rate()to accept both types - Update
CoagulationStrategyABC.net_rate()to accept both types - Update
CoagulationStrategyABC.step()to detect input type, process, and return matching type - Update
CoagulationStrategyABC.diffusive_knudsen()to accept both types - Update
CoagulationStrategyABC.coulomb_potential_ratio()to accept both types - Update
CoagulationStrategyABC.friction_factor()to accept both types - Update
BrownianCoagulationStrategy.kernel()to acceptUnion[ParticleRepresentation, ParticleData] - Update
ChargedCoagulationStrategy.kernel()similarly - Update
SedimentationCoagulationStrategy.kernel()similarly - Update
TurbulentShearCoagulationStrategy.kernel()similarly - Update
TurbulentDnsCoagulationStrategy.kernel()similarly - For
ParticleDatapath, use.radiiproperty for radius,.total_massfor mass,.concentrationfor concentration,.chargefor charge (with appropriate box_index=0 slicing) -
step()forParticleDatareturns updatedParticleData(new instance or mutated copy) -
step()forParticleRepresentationreturnsParticleRepresentation(existing behavior)
Testing (REQUIRED - Co-located with implementation)
- All existing tests in
particula/dynamics/coagulation/coagulation_strategy/tests/pass WITHOUT modification - New test:
test_brownian_kernel_with_particle_data- verifykernel()works withParticleData - New test:
test_brownian_step_with_particle_data- verifystep()works withParticleDataand returnsParticleData - New test:
test_brownian_step_returns_matching_types- verify legacy returns legacy, new returns new - New test:
test_loss_rate_with_particle_data- verifyloss_rate()works - New test:
test_gain_rate_with_particle_data- verifygain_rate()works - New test:
test_net_rate_with_particle_data- verifynet_rate()works - New test:
test_diffusive_knudsen_with_particle_data- verify helper works - All tests pass before merge
- Achieve 95%+ coverage on modified code
Technical Notes
Implementation Approach
The coagulation ABC methods access particle data via method calls like particle.get_radius(), particle.get_mass(), particle.concentration, particle.get_charge(). For ParticleData, the equivalents are:
| ParticleRepresentation | ParticleData (box_index=0) |
|---|---|
particle.get_radius() |
data.radii[0] |
particle.get_mass() |
data.total_mass[0] |
particle.get_species_mass() |
data.masses[0] |
particle.concentration |
data.concentration[0] |
particle.get_charge() |
data.charge[0] |
particle.get_volume() |
float(data.volume[0]) |
particle.get_density() |
data.density |
particle.get_effective_density() |
data.effective_density[0] |
The cleanest approach is an adapter pattern:
from typing import Union
from particula.particles.particle_data import ParticleData
from particula.particles.representation import ParticleRepresentation
def _get_radius(
particle: Union[ParticleRepresentation, ParticleData],
) -> NDArray[np.float64]:
"""Get radius array from either type."""
if isinstance(particle, ParticleData):
return particle.radii[0]
return particle.get_radius()
def _get_mass(
particle: Union[ParticleRepresentation, ParticleData],
) -> NDArray[np.float64]:
"""Get total mass array from either type."""
if isinstance(particle, ParticleData):
return particle.total_mass[0]
return particle.get_mass()
def _get_concentration(
particle: Union[ParticleRepresentation, ParticleData],
) -> NDArray[np.float64]:
"""Get concentration array from either type."""
if isinstance(particle, ParticleData):
return particle.concentration[0]
return particle.concentration
def _get_charge(
particle: Union[ParticleRepresentation, ParticleData],
) -> NDArray[np.float64]:
"""Get charge array from either type."""
if isinstance(particle, ParticleData):
return particle.charge[0]
return particle.get_charge()Key Design Decisions
- Adapter functions: Instead of modifying every method body, use adapter functions that normalize access patterns. This keeps the actual physics code unchanged.
- ParticleData single-box path: All coagulation methods assume single-box simulation when
ParticleDatais passed (use[0]indexing). - Particle-resolved coagulation:
step()fordistribution_type="particle_resolved"callsparticle.collide_pairs()and accessesparticle.get_volume(). TheParticleDatapath must implement equivalent mutations. CombineCoagulationStrategy: This composes multiple strategies. It should automatically support both types since it delegates to the individual strategies.
Critical Compatibility Notes
loss_rate()andgain_rate()directly accessparticle.concentration(not via a method) in some code paths - this is an attribute onParticleRepresentationbut a property onParticleDatastep()callsparticle.add_concentration()for discrete/continuous distributions -ParticleDatadoesn't have this method, so the P4 code must handle concentration updates directlystep()for particle-resolved callsparticle.collide_pairs()- forParticleData, equivalent mass/concentration merging must be performed
Integration Points
particula/dynamics/coagulation/coagulation_strategy/coagulation_strategy_abc.py- Main ABCparticula/dynamics/coagulation/coagulation_strategy/brownian_coagulation_strategy.py- Primary concrete strategyparticula/dynamics/coagulation/coagulation_rate.py- Rate functions take raw arrays (radius, concentration, kernel)particula/particles/particle_data.py-ParticleDatacontainer
Edge Cases and Considerations
- Particle-resolved coagulation:
collide_pairs()merges particles by modifying distribution, concentration, and charge. ForParticleData, this means directly updatingmasses,concentration,chargearrays at[0, idx, :]. - Zero-particle case: If
ParticleDatahasn_particles=0, coagulation should no-op gracefully. - CombineCoagulationStrategy: Multiple strategies composed - verify that dual-type support propagates through the combination.
- Kernel computation: Concrete strategies compute kernel from radius and mass. Adapter functions must provide these in the correct shape.
Example Usage
import numpy as np
from particula.particles.particle_data import ParticleData
from particula.dynamics.coagulation import BrownianCoagulationStrategy
particle_data = ParticleData(
masses=np.random.rand(1, 50, 2) * 1e-18,
concentration=np.ones((1, 50)) * 1e6,
charge=np.zeros((1, 50)),
density=np.array([1000.0, 1200.0]),
volume=np.array([1.0]),
)
strategy = BrownianCoagulationStrategy(distribution_type="discrete")
# Returns updated ParticleData
updated = strategy.step(particle_data, 298.15, 101325.0, 1.0)
assert isinstance(updated, ParticleData)References
Feature Plan:
adw-docs/dev-plans/features/E3-F4-facade-migration.md
Related Issues:
- [E3-F4] Facade and Migration #1020 - Parent feature issue
- [Phase E3-F4-P1] Create ParticleRepresentation facade over ParticleData with deprecation warnings and tests #1068 - E3-F4-P1: ParticleRepresentation facade (dependency)
- [Phase E3-F4-P2] Create GasSpecies facade over GasData with deprecation warnings and tests #1069 - E3-F4-P2: GasSpecies facade (dependency)
- [Phase E3-F4-P3] Update CondensationIsothermal to accept both old and new data types with tests #1070 - E3-F4-P3: Condensation dual-type (parallel)
Related Code:
particula/dynamics/coagulation/coagulation_strategy/coagulation_strategy_abc.py(477 lines) - Main ABCparticula/dynamics/coagulation/coagulation_strategy/brownian_coagulation_strategy.py- Brownian kernelparticula/dynamics/coagulation/coagulation_strategy/charged_coagulation_strategy.py- Charged kernelparticula/dynamics/coagulation/coagulation_strategy/sedimentation_coagulation_strategy.py- Sedimentationparticula/dynamics/coagulation/coagulation_strategy/turbulent_shear_coagulation_strategy.py- Turbulent shearparticula/dynamics/coagulation/coagulation_strategy/turbulent_dns_coagulation_strategy.py- Turbulent DNSparticula/dynamics/coagulation/coagulation_strategy/combine_coagulation_strategy.py- Combinedparticula/dynamics/coagulation/coagulation_strategy/tests/- Existing tests
Coding Standards:
adw-docs/code_style.md- Python standards (snake_case, 80-char lines)adw-docs/testing_guide.md- Testing patterns (*_test.py suffix)