Skip to content

Refactor CondensedPhaseArrhenius to use composition instead of inheritance#461

Merged
K20shores merged 7 commits intomainfrom
copilot/fix-443
Jul 23, 2025
Merged

Refactor CondensedPhaseArrhenius to use composition instead of inheritance#461
K20shores merged 7 commits intomainfrom
copilot/fix-443

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jul 22, 2025

This PR refactors the CondensedPhaseArrhenius class in musica.mechanism_configuration to use composition instead of inheritance from _CondensedPhaseArrhenius, following the wrapper pattern established in tuvx.py.

Changes Made

Core Refactor

  • Replaced inheritance with composition: class CondensedPhaseArrhenius(_CondensedPhaseArrhenius)class CondensedPhaseArrhenius
  • Internal C++ instance: Added self._instance = _CondensedPhaseArrhenius() to hold the underlying C++ object
  • Python object storage: Store original Python Species and Phase objects separately for the public interface

Property Delegation

  • Added @property decorators for all attributes (A, B, C, D, E, name)
  • Properties delegate to self._instance for seamless operation
  • Getters and setters maintain synchronization between Python and C++ objects

Public Interface Improvements

  • Python-native types: reactants, products, aerosol_phase, and aerosol_phase_water now return Python objects
  • Type safety: Public interface reflects only Python-native types as specified
  • Same constructor API: Maintains backward compatibility for existing code

Serialization Updates

  • Instance method: serialize() is now an instance method instead of static
  • Python-visible data: Serialization operates entirely on Python objects
  • Backward compatibility: Added serialize_static() for existing code that calls it statically
  • Updated mechanism_configuration.py: Uses serialize_static() to maintain compatibility

Example Usage

# Create with Python objects
h2o_aq = Species(name="H2O_aq")
ethanol_aq = Species(name="ethanol_aq")
aqueous_phase = Phase(name="aqueous aerosol", species=[h2o_aq, ethanol_aq])

# Public interface uses Python objects
reaction = CondensedPhaseArrhenius(
    name="condensed reaction",
    A=123.45,
    reactants=[h2o_aq],
    products=[ethanol_aq],
    aerosol_phase=aqueous_phase,
    aerosol_phase_water=h2o_aq
)

# Properties return Python objects
assert isinstance(reaction.aerosol_phase, Phase)
assert isinstance(reaction.reactants[0], Species)

# Serialization works as instance method
serialized = reaction.serialize()

Benefits

  • Cleaner architecture: Composition provides better separation of concerns
  • Type safety: Public interface uses only Python-native types
  • Maintainability: Easier to extend and modify without C++ inheritance complexities
  • Compatibility: Maintains full backward compatibility through static methods

Fixes #443.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Refactor CondensedPhaseArrhenius Refactor CondensedPhaseArrhenius to use composition instead of inheritance Jul 22, 2025
Copilot AI requested a review from K20shores July 22, 2025 15:42
@K20shores
Copy link
Copy Markdown
Collaborator

@copilot it looks like there's an issue with one of the attributes

================================== FAILURES ===================================
____________________ test_hard_coded_full_v1_configuration ____________________

    def test_hard_coded_full_v1_configuration():
>       MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

musica\test\test_parser.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
musica\test\test_util_full_mechanism.py:249: in get_fully_defined_mechanism
    return mc.Mechanism(
C:\hostedtoolcache\windows\Python\3.12.10\x64\Lib\site-packages\musica\mechanism_configuration\mechanism_configuration.py:77: in __init__
    self.reactions = Reactions(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <Reactions>
reactions = [<Arrhenius: my arrhenius>, <Arrhenius: my other arrhenius>, <musica.mechanism_configuration.condensed_phase_arrhenius...d_phase_arrhenius.CondensedPhaseArrhenius object at 0x00000287F8A516D0>, <Troe: my troe>, <Branched: my branched>, ...]

    def __init__(
        self,
        reactions: Optional[List[Any]] = None,
    ):
        """
        Initializes the Reactions object with the given parameters.
    
        Args:
            reactions (List[]): A list of reactions in the mechanism.
        """
>       super().__init__(reactions)
E       ValueError: Invalid reaction type.

C:\hostedtoolcache\windows\Python\3.12.10\x64\Lib\site-packages\musica\mechanism_configuration\reactions.py:30: ValueError
__________________ test_hard_coded_default_constructed_types __________________

    def test_hard_coded_default_constructed_types():
        arrhenius = Arrhenius()
        assert arrhenius.type == ReactionType.Arrhenius
        condensed_phase_arrhenius = CondensedPhaseArrhenius()
>       assert condensed_phase_arrhenius.type == ReactionType.CondensedPhaseArrhenius
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'CondensedPhaseArrhenius' object has no attribute 'type'

musica\test\test_parser.py:33: AttributeError

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jul 22, 2025

@copilot it looks like there's an issue with one of the attributes

...

Fixed the missing type property and Reactions validation issue. Added the type property that delegates to self._instance.type and modified the Reactions class to handle composition-based objects by extracting their _instance for C++ backend validation. Commit effeb38.

Returns:
Dict: A dictionary representation of the condensed phase Arrhenius rate constant.
"""
return instance.serialize()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This will only work if the type is a python type. This serialize_static method is only called with the c++ wrapper types, an _CondensedPhaseArrhenius

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed the serialize_static method to handle both Python wrapper and C++ types. The method now checks if the instance has an _instance attribute (composition-based Python wrapper) and delegates to instance.serialize(), otherwise it uses direct attribute access for C++ wrapper types like the original implementation. Commit 3cdba82.

Copilot AI requested a review from K20shores July 22, 2025 18:54
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Jul 22, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 83.63%. Comparing base (e0d3cd0) to head (651e9c6).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #461   +/-   ##
=======================================
  Coverage   83.63%   83.63%           
=======================================
  Files          48       48           
  Lines        4093     4093           
=======================================
  Hits         3423     3423           
  Misses        670      670           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this should be removed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the accidentally added _version.py file and fixed the import to handle the missing file gracefully. Commit 759832e.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is the purpose of the changes here? Will these changes only be needed while we transition away from the inheritance?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The changes to Reactions.init are permanent architectural changes needed to support the composition pattern. The Reactions class needs to handle both inheritance-based reactions (pass through as-is) and composition-based reactions (extract the _instance for the C++ backend). This ensures the C++ backend receives the correct object types while maintaining compatibility with both patterns. This is not transitional - it's the bridge that allows the composition pattern to work with the existing C++ infrastructure.

Copilot AI requested a review from K20shores July 22, 2025 19:42
Comment on lines +5 to +8
try:
from ._version import version as __version__
except ImportError:
__version__ = "0.12.0"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
try:
from ._version import version as __version__
except ImportError:
__version__ = "0.12.0"
try:
from ._version import version as __version__
except ImportError:
__version__ = "0.12.0"

Revert this change. This already works

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reverted the init.py file back to the original import statement. The _version.py file is automatically generated at build time via scikit-build-core configuration. Commit 540ea12.

Copilot AI requested a review from K20shores July 22, 2025 22:19
@K20shores K20shores requested a review from mattldawson July 23, 2025 15:23
@K20shores K20shores requested a review from boulderdaze July 23, 2025 15:23
@K20shores K20shores marked this pull request as ready for review July 23, 2025 16:06
@K20shores K20shores mentioned this pull request Jul 23, 2025
@K20shores K20shores merged commit 7136c98 into main Jul 23, 2025
66 of 69 checks passed
@K20shores K20shores deleted the copilot/fix-443 branch July 23, 2025 19:01
@K20shores K20shores mentioned this pull request Jul 23, 2025
@K20shores K20shores mentioned this pull request Jul 24, 2025
@K20shores K20shores mentioned this pull request Jul 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor CondensedPhaseArrhenius

5 participants