Skip to content

fix: #884 - [E2-F2-P1] Create EquilibriaStrategy ABC and LiquidVaporPartitioningStrategy#892

Merged
Gorkowski merged 3 commits intouncscode:mainfrom
Gorkowski:issue-884-adw-4a730a35
Jan 19, 2026
Merged

fix: #884 - [E2-F2-P1] Create EquilibriaStrategy ABC and LiquidVaporPartitioningStrategy#892
Gorkowski merged 3 commits intouncscode:mainfrom
Gorkowski:issue-884-adw-4a730a35

Conversation

@Gorkowski
Copy link
Collaborator

Target Branch: main

Fixes #884 | Workflow: 4a730a35

Summary

Introduces the EquilibriaStrategy ABC and LiquidVaporPartitioningStrategy to give the equilibria module a Strategy-pattern entry point with structured outputs. The new strategy wraps the refactored partitioning helpers, exposes typed dataclasses, and ships co-located tests so that future builders/factories can rely on a stable API.

What Changed

New Components

  • particula/equilibria/equilibria_strategies.py - Defines PhaseConcentrations, EquilibriumResult, the EquilibriaStrategy ABC, and the LiquidVaporPartitioningStrategy implementation with validation, conversion helpers, and Google-style docstrings referencing Gorkowski et al. (2019).
  • particula/equilibria/tests/equilibria_strategies_test.py - Exercises the new strategy with default/custom water activity, error propagation, beta-phase absence, malformed tuples, and regression comparisons to the existing partitioning helpers.

Modified Components

  • particula/equilibria/tests/partitioning_test.py - Leaves the reusable _build_partitioning_inputs helper to continue supporting the new strategy tests (imports reorganized around the helper).

Tests Added/Updated

  • particula/equilibria/tests/equilibria_strategies_test.py - Validates dataclasses, get_name(), water-activity bounds, conversion helpers, empty-input guard, and error propagation.

How It Works

LiquidVaporPartitioningStrategy.solve() now orchestrates the helper routines and shapes their outputs into typed dataclasses:

LiquidVaporPartitioningStrategy.solve()
    ├──▶ get_properties_for_liquid_vapor_partitioning(...)
    └──▶ liquid_vapor_partitioning(...)
             └──▶ _convert_to_result(alpha, beta, system, error)

The conversion helper enforces tuple shapes, ensures species/value alignment, derives PhaseConcentrations, and normalizes the error from either the system output or optimizer result.

Implementation Notes

  • Choice rationale: Strategy pattern mirrors the dynamics module and provides a single entry point that can later be consumed by builders or runnables.
  • Error handling: Water-activity bounds, empty inputs, shape mismatches, and partition coefficient alignments raise descriptive ValueErrors or propagate the finite error returned from the helper routines.
  • Docstrings: All new public classes and methods include Google-style docstrings with references so documentation coverage stays at 100% for the new module.

Testing

  • pytest particula/equilibria/tests/equilibria_strategies_test.py

Successfully fixed:
- Added EquilibriaStrategy and PhaseConcentrations/EquilibriumResult dataclasses to capture structured outputs
- Wrapped liquid-vapor partitioning in LiquidVaporPartitioningStrategy with input validation, error propagation, and conversion helpers
- Exported the strategy API and extended partitioning tests to cover new validation paths and dataclasses

Still failing:
- None

ADW-ID: 4a730a35
Include the additional helper outputs in partitioning test fixtures so formatting and docstrings stay aligned with lint rules.

Closes uncscode#884

ADW-ID: 4a730a35
Copilot AI review requested due to automatic review settings January 19, 2026 19:42
@Gorkowski Gorkowski added agent Created or managed by ADW automation blocked Blocked - review required before ADW can process labels Jan 19, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a Strategy pattern architecture to the equilibria module by creating an EquilibriaStrategy abstract base class and implementing LiquidVaporPartitioningStrategy as the first concrete strategy. The new design wraps existing partitioning functions with structured dataclass outputs (EquilibriumResult and PhaseConcentrations), providing a clean API for future builders and factories while maintaining compatibility with existing functionality.

Changes:

  • Created EquilibriaStrategy ABC with abstract solve() method and concrete get_name() method
  • Implemented LiquidVaporPartitioningStrategy that wraps liquid-vapor partitioning with water activity configuration
  • Added PhaseConcentrations and EquilibriumResult dataclasses for structured outputs
  • Extended _build_partitioning_inputs helper to return oxygen2carbon and density for strategy tests
  • Exported new classes in equilibria/__init__.py

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.

File Description
particula/equilibria/equilibria_strategies.py Defines the ABC, dataclasses, and concrete liquid-vapor strategy with validation and conversion helpers
particula/equilibria/tests/equilibria_strategies_test.py Comprehensive tests covering initialization, water activity validation, shape checking, regression comparison, error propagation, and edge cases
particula/equilibria/tests/partitioning_test.py Updated existing tests to handle additional return values (oxygen2carbon, density) from modified helper function
particula/equilibria/__init__.py Added exports for new strategy classes and dataclasses

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

beta_phase = self._to_phase(beta) if beta is not None else None

if (
partition_coefficients.shape
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

The condition on line 211 is redundant. The expression partition_coefficients.shape and partition_coefficients.shape != alpha_phase.species_concentrations.shape will always evaluate the second part when the first part is truthy, because a non-empty tuple is always truthy in Python. The first partition_coefficients.shape check doesn't add meaningful protection. Consider simplifying to just if partition_coefficients.shape != alpha_phase.species_concentrations.shape: or if you need to handle scalar cases, use if partition_coefficients.ndim > 0 and partition_coefficients.shape != alpha_phase.species_concentrations.shape:

Suggested change
partition_coefficients.shape
partition_coefficients.ndim > 0

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +264
@staticmethod
def _extract_error(
system: SystemOutput, fit_result: OptimizeResult
) -> float:
if len(system) >= 4 and np.isfinite(system[3]):
return float(system[3])
fit_value = getattr(fit_result, "fun", np.nan)
if np.isfinite(fit_value):
return float(fit_value)
raise ValueError("Unable to determine finite error from system output")
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

The error handling case where both system[3] and fit_result.fun are non-finite is not covered by tests. Consider adding a test case that verifies the ValueError is raised when both error sources are NaN or infinite, to ensure this edge case is properly handled.

Copilot uses AI. Check for mistakes.
def test_convert_to_result_rejects_malformed_tuple(inputs):
"""Reject malformed phase/system tuples in the conversion helper."""
(
c_star_j_dry,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable c_star_j_dry is not used.

Suggested change
c_star_j_dry,
_c_star_j_dry,

Copilot uses AI. Check for mistakes.
"""Reject malformed phase/system tuples in the conversion helper."""
(
c_star_j_dry,
concentration_organic_matter,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable concentration_organic_matter is not used.

Suggested change
concentration_organic_matter,
_,

Copilot uses AI. Check for mistakes.
(
c_star_j_dry,
concentration_organic_matter,
molar_mass,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable molar_mass is not used.

Suggested change
molar_mass,
_molar_mass,

Copilot uses AI. Check for mistakes.
c_star_j_dry,
concentration_organic_matter,
molar_mass,
oxygen2carbon,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable oxygen2carbon is not used.

Suggested change
oxygen2carbon,
_oxygen2carbon,

Copilot uses AI. Check for mistakes.
Comment on lines +208 to +209
oxygen2carbon,
density,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable density is not used.

Suggested change
oxygen2carbon,
density,
_oxygen2carbon,
_density,

Copilot uses AI. Check for mistakes.
molar_mass,
oxygen2carbon,
density,
_gamma,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable _gamma is not used.

Copilot uses AI. Check for mistakes.
oxygen2carbon,
density,
_gamma,
_mass_fraction,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable _mass_fraction is not used.

Suggested change
_mass_fraction,
_,

Copilot uses AI. Check for mistakes.
density,
_gamma,
_mass_fraction,
_q_ab,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable _q_ab is not used.

Copilot uses AI. Check for mistakes.
Co-authored-by: Gorkowski <10118307+Gorkowski@users.noreply.github.com>
@Gorkowski Gorkowski merged commit bce0578 into uncscode:main Jan 19, 2026
7 checks passed
@Gorkowski Gorkowski deleted the issue-884-adw-4a730a35 branch January 19, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Created or managed by ADW automation blocked Blocked - review required before ADW can process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[E2-F2-P1] Create EquilibriaStrategy ABC and LiquidVaporPartitioningStrategy

2 participants