Skip to content

Reaction Equivalence

reactions_equivalent(reaction1, reaction2)

Check if two reactions are equivalent.

Two reactions are equivalent if they meet the following conditions:

  1. They have the same number of reactants.
  2. They have the same initial assembly, and the same entering assembly (or both have none).
  3. The binding sites involved in the exchange are equivalent:
    • Intra-molecular: the trio [metal, leaving, entering] must be equivalent.
    • Inter-molecular: the pair [metal, leaving] of the initial assembly must be equivalent, and the entering binding site of the entering assembly must be equivalent to that of the other reaction.

Here, two binding site lists (e.g. [metal1, leaving1] and [metal2, leaving2]) are equivalent if there is at least one isomorphism which maps each binding site in the first list to a binding site in the second list. The order of the binding sites in the list DOES matter.

Parameters:

Name Type Description Default
reaction1 IntraReaction | InterReaction

The first reaction to compare.

required
reaction2 IntraReaction | InterReaction

The second reaction to compare.

required

Returns:

Type Description
bool

True if the reactions are equivalent, False otherwise.

Notes

Reaction equivalence can be determined only by the left-hand side information (i.e., initial assembly, entering assembly, and MLE), so the right-hand side information (i.e., product assembly, leaving assembly) is not considered in this function.

Source code in src/nasap_net/reaction_equivalence/core.py
def reactions_equivalent(
        reaction1: Reaction,
        reaction2: Reaction,
        ) -> bool:
    """
    Check if two reactions are equivalent.

    Two reactions are equivalent if they meet the following conditions:

    1. They have the same number of reactants.
    2. They have the same initial assembly, and the same entering assembly
       (or both have none).
    3. The binding sites involved in the exchange are equivalent:
        - **Intra-molecular**: the trio ``[metal, leaving, entering]`` must
          be equivalent.
        - **Inter-molecular**: the pair ``[metal, leaving]`` of the initial
          assembly must be equivalent, and the entering binding site of the
          entering assembly must be equivalent to that of the other reaction.

    Here, two binding site lists (e.g. [metal1, leaving1] and 
    [metal2, leaving2]) are equivalent if there is at least one isomorphism
    which maps each binding site in the first list to a binding site in 
    the second list. The order of the binding sites in the list DOES matter.

    Parameters
    ----------
    reaction1 : IntraReaction | InterReaction
        The first reaction to compare.
    reaction2 : IntraReaction | InterReaction
        The second reaction to compare.

    Returns
    -------
    bool
        True if the reactions are equivalent, False otherwise.

    Notes
    -----
    Reaction equivalence can be determined only by the left-hand side
    information (i.e., initial assembly, entering assembly, and MLE),
    so the right-hand side information (i.e., product assembly, leaving assembly)
    is not considered in this function.
    """
    if reaction1 == reaction2:
        return True

    # Condition 1: Same number of reactants
    if reaction1.is_inter() != reaction2.is_inter():
        return False

    # Condition 2: Same assemblies (only left-hand side)
    if not assemblies_equivalent(reaction1.init_assem, reaction2.init_assem):
        return False
    if reaction1.is_inter():
        assert reaction2.is_inter()
        if not assemblies_equivalent(
                reaction1.entering_assem_strict,
                reaction2.entering_assem_strict
        ):
            return False

    # Condition 3: Equivalent pair/trio of binding sites
    if reaction1.is_intra():
        if not intra_reaction_mles_equivalent(reaction1, reaction2):
            return False
    else:
        assert reaction1.is_inter()
        if not inter_reaction_mles_equivalent(reaction1, reaction2):
            return False
    return True

compute_reaction_list_diff(reactions1, reactions2)

Compute the difference between two lists of reactions.

Parameters:

Name Type Description Default
reactions1 Iterable[Reaction]

The first list of reactions.

required
reactions2 Iterable[Reaction]

The second list of reactions.

required

Returns:

Type Description
ReactionListDiff

An object containing reactions only in the first list and reactions only in the second list.

Source code in src/nasap_net/reaction_equivalence/reaction_list_diff.py
def compute_reaction_list_diff(
        reactions1: Iterable[Reaction],
        reactions2: Iterable[Reaction],
        ) -> ReactionListDiff:
    """
    Compute the difference between two lists of reactions.

    Parameters
    ----------
    reactions1 : Iterable[Reaction]
        The first list of reactions.
    reactions2 : Iterable[Reaction]
        The second list of reactions.

    Returns
    -------
    ReactionListDiff
        An object containing reactions only in the first list and
        reactions only in the second list.
    """
    sig_to_reactions1 = defaultdict(set)
    for reaction in reactions1:
        sig = get_reaction_signature(reaction)
        sig_to_reactions1[sig].add(reaction)

    sig_to_reactions2 = defaultdict(set)
    for reaction in reactions2:
        sig = get_reaction_signature(reaction)
        sig_to_reactions2[sig].add(reaction)

    first_only = set()
    second_only = set()

    common_sigs = sig_to_reactions1.keys() & sig_to_reactions2.keys()
    first_only_sigs = sig_to_reactions1.keys() - common_sigs
    second_only_sigs = sig_to_reactions2.keys() - common_sigs

    for first_only_sig in first_only_sigs:
        first_only.update(sig_to_reactions1[first_only_sig])
    for second_only_sig in second_only_sigs:
        second_only.update(sig_to_reactions2[second_only_sig])

    for common_sig in common_sigs:
        unpaired_reactions1 = sig_to_reactions1[common_sig].copy()
        unpaired_reactions2 = sig_to_reactions2[common_sig].copy()

        for reaction1 in sorted(unpaired_reactions1):
            for reaction2 in sorted(unpaired_reactions2):
                if reactions_equivalent(reaction1, reaction2):
                    unpaired_reactions1.remove(reaction1)
                    unpaired_reactions2.remove(reaction2)
                    break

        first_only.update(unpaired_reactions1)
        second_only.update(unpaired_reactions2)

    return ReactionListDiff(
        first_only=first_only,
        second_only=second_only,
    )