Convert exchange reactions in model to exchange compounds.

    Only exchange reactions in the extracellular compartment are converted.
    The extracelluar compartment must be defined for the model.

    Args:
        model: :class:`NativeModel`.

def convert_exchange_to_compounds(model):
    """Convert exchange reactions in model to exchange compounds.

    Only exchange reactions in the extracellular compartment are converted.
    The extracelluar compartment must be defined for the model.

    Args:
        model: :class:`NativeModel`.
    """
    # Build set of exchange reactions
    exchanges = set()
    for reaction in model.reactions:
        equation = reaction.properties.get('equation')
        if equation is None:
            continue

        if len(equation.compounds) != 1:
            # Provide warning for exchange reactions with more than
            # one compound, they won't be put into the exchange definition
            if (len(equation.left) == 0) != (len(equation.right) == 0):
                logger.warning('Exchange reaction {} has more than one'
                               ' compound, it was not converted to'
                               ' exchange compound'.format(reaction.id))
            continue

        exchanges.add(reaction.id)

    # Convert exchange reactions into exchange compounds
    for reaction_id in exchanges:
        equation = model.reactions[reaction_id].equation
        compound, value = equation.compounds[0]
        if compound.compartment != model.extracellular_compartment:
            continue

        if compound in model.exchange:
            logger.warning(
                'Compound {} is already defined in the exchange'
                ' definition'.format(compound))
            continue

        # We multiply the flux bounds by value in order to create equivalent
        # exchange reactions with stoichiometric value of one. If the flux
        # bounds are not set but the reaction is unidirectional, the implicit
        # flux bounds must be used.
        lower_flux, upper_flux = None, None
        if reaction_id in model.limits:
            _, lower, upper = model.limits[reaction_id]
            if lower is not None:
                lower_flux = lower * abs(value)
            if upper is not None:
                upper_flux = upper * abs(value)

        if lower_flux is None and equation.direction == Direction.Forward:
            lower_flux = 0
        if upper_flux is None and equation.direction == Direction.Reverse:
            upper_flux = 0

        # If the stoichiometric value of the reaction is reversed, the flux
        # limits must be flipped.
        if value > 0:
            lower_flux, upper_flux = (
                -upper_flux if upper_flux is not None else None,
                -lower_flux if lower_flux is not None else None)

        model.exchange[compound] = (
            compound, reaction_id, lower_flux, upper_flux)

        model.reactions.discard(reaction_id)
        model.limits.pop(reaction_id, None)