Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion adw-docs/dev-plans/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ and rollout.
- Scope: Thermal resistance factor and non-isothermal mass transfer rate
pure functions with energy tracking.
- [E5-F3: CondensationLatentHeat Strategy Class][e5-f3] — Status: In Progress
(P1, #1139)
(P1, #1139; P5, #1143)
- Scope: New condensation strategy with latent heat correction and energy
diagnostics.
- [E5-F4: Builder, Factory, and Exports][e5-f4] — Status: Planning
Expand Down
4 changes: 3 additions & 1 deletion adw-docs/dev-plans/epics/E5-non-isothermal-condensation.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
**Owners**: @Gorkowski
**Start Date**: 2026-03-02
**Target Date**: TBD
**Last Updated**: 2026-03-04
**Last Updated**: 2026-03-05
**Size**: Medium (7 features, ~22 phases)

## Vision
Expand Down Expand Up @@ -318,6 +318,7 @@ L -> 0 as T -> T_c. Used in engineering thermodynamics and EOS-based models.
when L=0

- [ ] **E5-F3-P5**: Add data-only path support and parity tests
- Issue: #1143 | Status: In Progress
- Ensure `ParticleData` + `GasData` input path works identically to
`ParticleRepresentation` + `GasSpecies` legacy path
- Follow the same pattern as `CondensationIsothermal.step()` lines
Expand Down Expand Up @@ -623,3 +624,4 @@ class CondensationLatentHeat(CondensationStrategy):
| 2026-03-02 | Initial epic creation | ADW |
| 2026-03-02 | Split E5-F1-P3 into P3 (builders) + P4 (factory+exports); split E5-F3-P3 into P3 (particle-resolved step) + P4 (discrete+continuous) + P5 (data-only parity); added missing details: function signatures, file references, thermal conductivity source, vapor_pressure_surface parameter, test tolerances, literature targets | ADW |
| 2026-03-04 | Noted E5-F3-P1 issue #1139 and logging expectations | ADW |
| 2026-03-05 | Marked E5-F3-P5 parity tests as issue #1143 | ADW |
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
**Owners**: @Gorkowski
**Start Date**: 2026-03-04
**Target Date**: TBD
**Last Updated**: 2026-03-04
**Last Updated**: 2026-03-05
**Size**: Large (5 phases)

## Summary
Expand Down Expand Up @@ -174,7 +174,7 @@ additions:
when L=0

- [ ] **E5-F3-P5**: Add data-only path support and parity tests
- Issue: TBD | Size: S (~50 LOC) | Status: Not Started
- Issue: #1143 | Size: S (~50 LOC) | Status: In Progress
- Ensure `ParticleData` + `GasData` input path works identically to
`ParticleRepresentation` + `GasSpecies` legacy path
- Follow the same pattern as `CondensationIsothermal.step()` lines
Expand Down Expand Up @@ -241,3 +241,4 @@ additions:
|------|--------|--------|
| 2026-03-02 | Initial feature document created from E5 epic | ADW |
| 2026-03-04 | Marked P1 in progress for issue #1139 | ADW |
| 2026-03-05 | Noted P5 parity tests issue #1143 | ADW |
2 changes: 2 additions & 0 deletions particula/dynamics/condensation/condensation_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ def _fill_zero_radius(
filled = self._fill_zero_radius(r)
```
"""
if radius.size == 0:
return radius
if np.max(radius) == 0.0:
message = (
"All radius values are zero, radius set to 1 m for "
Expand Down
15 changes: 13 additions & 2 deletions particula/dynamics/condensation/mass_transfer_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,20 @@ def calc_mass_to_change(
- "Mass transfer",
[Wikipedia](https://en.wikipedia.org/wiki/Mass_transfer)
"""
time_step_arr = np.asarray(time_step, dtype=np.float64)
if mass_rate.ndim == 2:
return mass_rate * time_step * particle_concentration[:, None]
return mass_rate * time_step * particle_concentration
if time_step_arr.ndim == 0:
time_step_broadcast = time_step_arr
elif time_step_arr.shape == (mass_rate.shape[0],):
# Per-particle timestep: align with the first axis
time_step_broadcast = time_step_arr[:, None]
elif time_step_arr.shape == (mass_rate.shape[1],):
# Per-species timestep fallback for backward compatibility
time_step_broadcast = time_step_arr[None, :]
else:
time_step_broadcast = time_step_arr
Comment on lines +54 to +63
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For 2D mass_rate, unsupported time_step shapes fall through to time_step_broadcast = time_step_arr, which can silently trigger unintended NumPy broadcasting (or produce hard-to-debug shape errors downstream). Consider explicitly validating allowed shapes (scalar, (n_particles,), (n_species,), or (n_particles, n_species) / broadcastable equivalents) and raising a clear ValueError when the shape is not compatible with mass_rate.

Suggested change
if time_step_arr.ndim == 0:
time_step_broadcast = time_step_arr
elif time_step_arr.shape == (mass_rate.shape[0],):
# Per-particle timestep: align with the first axis
time_step_broadcast = time_step_arr[:, None]
elif time_step_arr.shape == (mass_rate.shape[1],):
# Per-species timestep fallback for backward compatibility
time_step_broadcast = time_step_arr[None, :]
else:
time_step_broadcast = time_step_arr
try:
# Allow scalar, per-particle, per-species or full (particle, species)
# time steps, along with any NumPy-broadcastable equivalents.
time_step_broadcast = np.broadcast_to(time_step_arr, mass_rate.shape)
except ValueError as exc:
raise ValueError(
"time_step shape is not compatible with 2D mass_rate. "
f"Got time_step.shape={time_step_arr.shape}, expected a scalar "
f"or an array broadcastable to {mass_rate.shape} (e.g. "
"(n_particles,), (n_species,), (n_particles, n_species))."
) from exc

Copilot uses AI. Check for mistakes.
return mass_rate * time_step_broadcast * particle_concentration[:, None]
return mass_rate * time_step_arr * particle_concentration


def apply_condensation_limit(
Expand Down
Loading
Loading