-
Notifications
You must be signed in to change notification settings - Fork 10
Description
Dependencies:
#1017 [E3-F1] ──┐
#1018 [E3-F2] ──┤
├──► #THIS [E3-F4-P2]
#1019 [E3-F3] ──┤
#1068 [E3-F4-P1]┘
Parent Issue: #1020
Dependencies:
- E3-F1: [E3-F1] Particle Data Container #1017
- E3-F2: [E3-F2] Gas Data Container #1018
- E3-F3: [E3-F3] Warp Integration and GPU Kernels #1019
- E3-F4-P1: [Phase E3-F4-P1] Create ParticleRepresentation facade over ParticleData with deprecation warnings and tests #1068
Description
Refactor GasSpecies to become a facade that wraps GasData internally while maintaining the exact same public API. All existing code that uses GasSpecies must continue to work without modification. Add deprecation warnings to the constructor to guide users toward GasData.
Context
This is the second phase of the E3-F4 Facade and Migration feature. The new GasData container (from E3-F2 #1018) provides a clean data-only container with batch dimensions for multi-box CFD simulations. The GasSpecies class (506 lines) currently combines data storage with vapor pressure strategy behavior. This phase separates them by making GasSpecies a facade over GasData.
Parent Issue: #1020 (E3-F4: Facade and Migration)
Blocked by: #1017, #1018, #1019 (E3 data containers), #1068 (E3-F4-P1 ParticleRepresentation facade, for pattern consistency)
Value:
- Enables gradual migration from
GasSpeciestoGasData - Separates data from behavior (vapor pressure strategies)
- Deprecation warnings guide users toward the preferred
GasDataAPI
Scope
Estimated Lines of Code: ~150 lines (excluding tests)
Complexity: Medium
Files to Modify:
particula/gas/species.py(~150 LOC changes) - Refactor class to facade wrappingGasData
Files to Create:
particula/gas/tests/species_facade_test.py(~120 LOC) - New facade-specific tests
Acceptance Criteria
Core Implementation
- Refactor
GasSpecies.__init__()to create an internalGasDatainstance (self._data) from name, molar_mass, concentration, and partitioning - Add
DeprecationWarningto__init__()with message directing users toGasDataand migration guide - Delegate
get_name()toself._data.name - Delegate
get_molar_mass()toself._data.molar_mass - Delegate
get_concentration()toself._data.concentration[0](single box, adapting 2D to scalar/1D) - Delegate
get_partitioning()toself._data.partitioning - Keep
pure_vapor_pressure_strategyas an attribute on the facade (behavior, not data) - Ensure
get_pure_vapor_pressure(),get_partial_pressure(),get_saturation_ratio(), andget_saturation_concentration()continue to work using the strategy + delegated data - Ensure
add_concentration()updatesself._data.concentrationinternally - Ensure
set_concentration()updatesself._data.concentrationinternally - Ensure
append()updatesself._databy creating a new mergedGasData - Ensure
__iadd__()and__add__()work correctly with the facade - Ensure
__len__()returns correct count viaself._data.n_species - Ensure
__str__()returns unchanged output - Add
dataproperty that returns the underlyingGasDatainstance - Add
from_data()classmethod that creates a facade from an existingGasData+ strategies without deprecation warning - Maintain backward compatibility for the
self.molar_mass,self.concentration,self.namedirect attribute access patterns used throughout the codebase (these are accessed directly in condensation strategies)
Conversion Helpers
- The existing
from_species()function ingas_data.pymust continue to work with the facade - The existing
to_species()function ingas_data.pymust continue to work
Testing (REQUIRED - Co-located with implementation)
- All existing tests in
particula/gas/tests/species_test.pypass WITHOUT modification (critical backward compatibility) - New test:
test_init_deprecation_warning- verifyDeprecationWarningon constructor - New test:
test_data_property_returns_gas_data- verify.datareturnsGasDatainstance - New test:
test_from_data_no_deprecation_warning- verifyfrom_data()does NOT trigger warning - New test:
test_facade_delegation_get_name- verify name delegation - New test:
test_facade_delegation_get_molar_mass- verify molar mass delegation - New test:
test_facade_delegation_get_concentration- verify concentration delegation - New test:
test_add_concentration_updates_internal_data- verifyadd_concentration()updatesself._data - New test:
test_set_concentration_updates_internal_data- verifyset_concentration()updatesself._data - New test:
test_append_updates_internal_data- verifyappend()creates mergedGasData - New test:
test_multi_species_facade- verify facade works with multiple appended species - All tests pass before merge
- Achieve 95%+ coverage on modified code
Technical Notes
Implementation Approach
The facade keeps the existing constructor signature but internally creates a GasData instance. Vapor pressure strategies stay on the facade as behavior:
import warnings
from particula.gas.gas_data import GasData
class GasSpecies:
@validate_inputs({"molar_mass": "positive"})
def __init__(self, name, molar_mass, vapor_pressure_strategy=...,
partitioning=True, concentration=0.0):
warnings.warn(
"GasSpecies is deprecated. Use GasData instead. "
"See migration guide: docs/migration/particle-data.md",
DeprecationWarning,
stacklevel=2,
)
# Keep strategy (behavior stays on facade)
self.pure_vapor_pressure_strategy = vapor_pressure_strategy
# Normalize to lists/arrays for GasData
names = [name] if isinstance(name, str) else list(name)
mm = np.atleast_1d(np.asarray(molar_mass, dtype=np.float64))
conc = np.atleast_1d(np.asarray(concentration, dtype=np.float64))
part = np.array([partitioning] * len(names), dtype=np.bool_)
self._data = GasData(
name=names,
molar_mass=mm,
concentration=conc.reshape(1, -1), # (1, n_species)
partitioning=part,
)
@property
def data(self) -> GasData:
return self._data
# Backward-compat attribute access (used by condensation)
@property
def molar_mass(self):
if self._data.n_species == 1:
return float(self._data.molar_mass[0])
return self._data.molar_mass
@property
def concentration(self):
conc = self._data.concentration[0] # box_index=0
if self._data.n_species == 1:
return float(conc[0])
return concKey Design Decisions
- Single-box assumption: The facade always uses
box_index=0, since the old API has no batch dimension. - Attribute access compatibility:
self.molar_massandself.concentrationare widely accessed as direct attributes in condensation strategies (e.g.,gas_species.molar_masson line 1168 ofcondensation_strategies.py). These MUST remain accessible as properties. - Strategy preservation: Vapor pressure strategies remain on the facade, not in
GasData(data-only container).
Integration Points
- Uses
particula/gas/gas_data.py:GasData(from E3-F2 [E3-F2] Gas Data Container #1018) from_species()andto_species()conversion functions ingas_data.pymust continue working- Condensation strategies directly access
gas_species.molar_mass,gas_species.concentration,gas_species.pure_vapor_pressure_strategy- these MUST remain accessible CondensationIsothermalStaggered._calculate_single_particle_transfer()accessesgas_species.pure_vapor_pressure_strategyas both single strategy and list
Edge Cases and Considerations
- Scalar vs array molar_mass/concentration:
GasSpeciessupports bothfloatandNDArrayfor molar_mass and concentration. The facade must handle seamless conversion. - Negative concentration clamping:
_check_if_negative_concentration()must still clamp to 0 and update_data - Strategy as list: When species are appended,
pure_vapor_pressure_strategybecomes a list. This must remain compatible. - Direct attribute writes: Some code may write
gas_species.concentration = new_valuedirectly. The property setter must update_data.
Example Usage
import warnings
import numpy as np
from particula.gas.species import GasSpecies
from particula.gas.vapor_pressure_strategies import ConstantVaporPressureStrategy
# Old code still works (with deprecation warning)
with warnings.catch_warnings():
warnings.simplefilter("always")
species = GasSpecies(
name="Water",
molar_mass=0.018,
vapor_pressure_strategy=ConstantVaporPressureStrategy(2330),
partitioning=True,
concentration=1e-3,
)
# Access new data container
gas_data = species.data # Returns GasData
print(gas_data.n_boxes) # 1
print(gas_data.n_species) # 1
print(gas_data.concentration.shape) # (1, 1)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 (pattern reference)
- [E3-F1] Particle Data Container #1017 - E3-F1: ParticleData container (dependency)
- [E3-F2] Gas Data Container #1018 - E3-F2: GasData container (dependency)
Related Code:
particula/gas/species.py(506 lines) - Current implementation to refactorparticula/gas/gas_data.py(300 lines) - New data container to wrapparticula/gas/tests/species_test.py- Existing tests that MUST passparticula/gas/tests/gas_data_test.py- GasData tests for referenceparticula/dynamics/condensation/condensation_strategies.py- Accesses gas_species attributes directly
Coding Standards:
adw-docs/code_style.md- Python standards (snake_case, 80-char lines)adw-docs/testing_guide.md- Testing patterns (*_test.py suffix)