PyCoMo Loopless FVA

This tutorial show-cases the use of loopless FVA.

The expected runtime for this notebook is less than 10 minutes.

[1]:
from pathlib import Path
import sys
import os
import cobra
import matplotlib.pyplot as plt
from cobra import Reaction, Metabolite, Model
from cobra.flux_analysis.loopless import add_loopless, loopless_solution
import pandas as pd
import numpy as np
import math
import time
import warnings
[2]:
import pycomo
pycomo.configure_logger(level="info")
2025-11-24 15:59:11,155 - PyCoMo - INFO - Logger initialized.

Community toy model

This simple community toy model consists of two identical community members. Each member has a two step conversion of the starting metabolite A. The products of these conversions (B and C) can be secreted and taken up from the medium, thus allowing exchange and forming a cycle. The members also have a biomass reaction with substrates C and D (an additional substrate).

[3]:
model_single = Model()
model_single.add_metabolites([Metabolite(i) for i in "ABCD"])

for met in model_single.metabolites:
    met.compartment = "c"

model_single.add_metabolites([Metabolite(i+"_e") for i in "ABCD"])

for i in "ABCD":
    model_single.metabolites.get_by_id(i+"_e").compartment = "e"

model_single.add_reactions([Reaction(i) for i in ["EX_A", "EX_B", "EX_C", "EX_D", "TP_A", "TP_B", "TP_C", "TP_D", "bio", "v1", "v2"]])

model_single.reactions.EX_A.add_metabolites({"A_e": 1})
model_single.reactions.EX_B.add_metabolites({"B_e": 1})
model_single.reactions.EX_C.add_metabolites({"C_e": 1})
model_single.reactions.EX_D.add_metabolites({"D_e": 1})
model_single.reactions.TP_A.add_metabolites({"A_e": -1, "A": 1})
model_single.reactions.TP_B.add_metabolites({"B_e": -1, "B": 1})
model_single.reactions.TP_C.add_metabolites({"C_e": -1, "C": 1})
model_single.reactions.TP_D.add_metabolites({"D_e": -1, "D": 1})
model_single.reactions.bio.add_metabolites({"C": -1, "D": -1})

model_single.reactions.v1.add_metabolites({"A": -1, "B": 1})
model_single.reactions.v2.add_metabolites({"B": -1, "C": 1})

model_single.reactions.TP_B.lower_bound = -500
model_single.reactions.TP_C.lower_bound = -500
model_single.reactions.v2.lower_bound = -1000

model_single.objective = 'bio'

Constructing the community model

[4]:
single_org_models_toy = []
for name in ["model_a", "model_b"]:
    print(name)
    single_org_model = pycomo.SingleOrganismModel(model_single, name)
    single_org_models_toy.append(single_org_model)
model_a
model_b
[5]:
community_name = "toy_com"
com_model_obj_toy = pycomo.CommunityModel(single_org_models_toy, community_name)
com_model_obj_toy.convert_to_fixed_abundance()
2025-11-24 15:59:11,255 - PyCoMo - INFO - No community model generated yet. Generating now:
2025-11-24 15:59:11,263 - PyCoMo - INFO - Identified biomass reaction from objective: bio
2025-11-24 15:59:11,265 - PyCoMo - INFO - Note: no products in the objective function, adding biomass to it.
2025-11-24 15:59:11,296 - PyCoMo - INFO - Identified biomass reaction from objective: bio
2025-11-24 15:59:11,296 - PyCoMo - INFO - Note: no products in the objective function, adding biomass to it.
2025-11-24 15:59:11,329 - PyCoMo - WARNING - No annotation overlap found for matching several metabolites (4). Please make sure that the matched metabolites are indeed representing the same substance in all models! The list of metaboliteswithout annotation overlap can be accessed via 'model.no_annotation_overlap'
2025-11-24 15:59:11,338 - PyCoMo - WARNING - There are 20 metabolites without elements in the model. Mass balance checks may be unreliable.
2025-11-24 15:59:11,339 - PyCoMo - INFO - Generated community model.
[6]:
com_model_obj_toy.medium = {'EX_A_medium': 1000.0, 'EX_D_medium': 1000.0}
com_model_obj_toy.apply_medium()
[6]:
Name toy_com
Memory address 1d4ba00ead0
Number of metabolites 64
Number of reactions 71
Number of genes 0
Number of groups 0
Objective expression 1.0*community_biomass - 1.0*community_biomass_reverse_44dc1
Compartments model_a_c, model_a_e, model_a_medium, medium, fraction_reaction, model_b_e, model_b_c, model_b_medium
[7]:
com_model_obj_toy.summary()
[7]:

Objective

1.0 community_biomass = 1000.0000000000001

Uptake

Metabolite Reaction Flux C-Number C-Flux
A_medium EX_A_medium 1000 0 0.00%
D_medium EX_D_medium 1000 0 0.00%

Secretion

Metabolite Reaction Flux C-Number C-Flux
cpd11416_medium community_biomass -1000 0 0.00%

Loops in the toy model

[8]:
com_model_obj_toy.get_loops()
2025-11-24 15:59:11,447 - PyCoMo - INFO - Removing 3 fraction reactions
2025-11-24 15:59:11,448 - PyCoMo - INFO - Removing 38 metabolites
2025-11-24 15:59:18,579 - PyCoMo - INFO - Processed 100.0% of find loops steps
2025-11-24 15:59:18,580 - PyCoMo - INFO - While pending closed!
2025-11-24 15:59:18,659 - PyCoMo - INFO - Loop detection finished
[8]:
reaction min_flux max_flux
0 model_a_TF_B_model_a_e -500.0 500.0
1 model_a_TF_C_model_a_e -500.0 500.0
2 model_a_TP_B -500.0 500.0
3 model_a_TP_C -500.0 500.0
4 model_a_v2_model_a_c -500.0 500.0
5 model_b_TF_B_model_b_e -500.0 500.0
6 model_b_TF_C_model_b_e -500.0 500.0
7 model_b_TP_B -500.0 500.0
8 model_b_TP_C -500.0 500.0
9 model_b_v2_model_b_c -500.0 500.0

FVA examples

FVA with loops

Normal fva will include the loops including metabolites B and C, reaction v2 and the transporters of B and C in the solutions.

The following scenarios and correct outcomes will be tested on a :

  • No medium: no reaction can carry flux

  • 100% objective value: reaction v2 carries maximum flux (500; = 1000 * 0.5 as normalized to equal abundance) and transport reactions of B and C are inactive

  • 0% objective value: reactions v2 and transporters of B and C carry maximum flux (250, reverse also for transporters)

  • 80% objective value: reaction v2 carries flux between 300 and 500. Transporter of B can be active between -200 and 200, transporter of C between -100 and 100. This solution arises by one member giving 200 of B to the other (keeping 300), which converts B to C with maximum flux of 500, then gives back 100 of C while keeping the required 400. This results in a biomass reaction of 400 for both (80% of 500).

COBRApy fva

The loopless version of COBRApy cannot be used on PyCoMo community models, due to their bound-free reaction structure. The resulting solutions include the loop:

[9]:
%%time
with com_model_obj_toy.model:
    com_model_obj_toy.medium = {}
    com_model_obj_toy.apply_medium()
    fva = cobra.flux_analysis.flux_variability_analysis(com_model_obj_toy.model,
                                                        com_model_obj_toy.model.reactions,
                                                        fraction_of_optimum=0.,
                                                        loopless=True)
fva
CPU times: total: 156 ms
Wall time: 10.1 s
[9]:
minimum maximum
model_a_TF_A_model_a_e 0.0 0.0
model_a_TF_B_model_a_e -250.0 250.0
model_a_TF_C_model_a_e -250.0 250.0
model_a_TF_D_model_a_e 0.0 0.0
model_a_TP_A 0.0 0.0
... ... ...
SK_model_b_v2_model_b_c_ub 2.5 7.5
SK_model_b_to_community_biomass_ub 5.0 5.0
f_final 1.0 1.0
abundance_reaction 0.0 0.0
community_biomass 0.0 0.0

71 rows × 2 columns

PyCoMo fva with loops

The PyCoMo wrapper for fva will also include loops, when loopless mode is not activated

[10]:
%%time
com_model_obj_toy.medium = {'EX_A_medium': 1000.0, 'EX_D_medium': 1000.0}
com_model_obj_toy.apply_medium()
with com_model_obj_toy.model:
    com_model_obj_toy.medium = {}
    com_model_obj_toy.apply_medium()
    fva = com_model_obj_toy.run_fva(fraction_of_optimum=1., loopless=False)
fva
[<Reaction model_a_TF_A_model_a_e at 0x1d4ba03a950>, <Reaction model_a_TF_B_model_a_e at 0x1d4ba03ab10>, <Reaction model_a_TF_C_model_a_e at 0x1d4ba03ac90>, <Reaction model_a_TF_D_model_a_e at 0x1d4ba03ae10>, <Reaction EX_A_medium at 0x1d4b9dbb550>, <Reaction EX_B_medium at 0x1d4b9ee12d0>, <Reaction EX_C_medium at 0x1d4ba04a410>, <Reaction EX_D_medium at 0x1d4b9f703d0>, <Reaction model_b_TF_A_model_b_e at 0x1d4ba07f210>, <Reaction model_b_TF_B_model_b_e at 0x1d4ba0e8790>, <Reaction model_b_TF_C_model_b_e at 0x1d4ba0e8850>, <Reaction model_b_TF_D_model_b_e at 0x1d4ba0e8950>]
2025-11-24 15:59:32,314 - PyCoMo - INFO - Processed 100.0% of fva steps
CPU times: total: 62.5 ms
Wall time: 3.56 s
[10]:
reaction_id min_flux max_flux
model_a_TF_A_model_a_e model_a_TF_A_model_a_e 0.0 0.0
model_a_TF_B_model_a_e model_a_TF_B_model_a_e -250.0 250.0
model_a_TF_C_model_a_e model_a_TF_C_model_a_e -250.0 250.0
model_a_TF_D_model_a_e model_a_TF_D_model_a_e 0.0 0.0
EX_A_medium EX_A_medium 0.0 0.0
EX_B_medium EX_B_medium 0.0 0.0
EX_C_medium EX_C_medium 0.0 0.0
EX_D_medium EX_D_medium 0.0 0.0
model_b_TF_A_model_b_e model_b_TF_A_model_b_e 0.0 0.0
model_b_TF_B_model_b_e model_b_TF_B_model_b_e -250.0 250.0
model_b_TF_C_model_b_e model_b_TF_C_model_b_e -250.0 250.0
model_b_TF_D_model_b_e model_b_TF_D_model_b_e 0.0 0.0

PyCoMo loopless fva

The following examples show that the loopless fva implemented in PyCoMo leads to the correct solutions in the 4 test cases (no medium, 100% objective value, 0% objective value, 80% objective value)

Note: For larger (genome-scale, >4 members) it will be beneficial to use parallel processing for loopless FVA. The number of processes can be specified either using the cobrapy configuration object, or directly in the functions of PyCoMo, with the processes argument.

[11]:
%%time
with com_model_obj_toy.model:
    com_model_obj_toy.medium = {}
    com_model_obj_toy.apply_medium()
    fva = com_model_obj_toy.run_fva(fraction_of_optimum=0., loopless=True)
fva
[<Reaction model_a_TF_A_model_a_e at 0x1d4ba03a950>, <Reaction model_a_TF_B_model_a_e at 0x1d4ba03ab10>, <Reaction model_a_TF_C_model_a_e at 0x1d4ba03ac90>, <Reaction model_a_TF_D_model_a_e at 0x1d4ba03ae10>, <Reaction EX_A_medium at 0x1d4b9dbb550>, <Reaction EX_B_medium at 0x1d4b9ee12d0>, <Reaction EX_C_medium at 0x1d4ba04a410>, <Reaction EX_D_medium at 0x1d4b9f703d0>, <Reaction model_b_TF_A_model_b_e at 0x1d4ba07f210>, <Reaction model_b_TF_B_model_b_e at 0x1d4ba0e8790>, <Reaction model_b_TF_C_model_b_e at 0x1d4ba0e8850>, <Reaction model_b_TF_D_model_b_e at 0x1d4ba0e8950>]
2025-11-24 15:59:35,936 - PyCoMo - INFO - worker 22840: Loop correction will be applied on model_a_TF_A_model_a_e
2025-11-24 15:59:35,938 - PyCoMo - INFO - worker 25520: Loop correction will be applied on model_a_TF_B_model_a_e
2025-11-24 15:59:35,942 - PyCoMo - INFO - worker 22840: Loop correction will be applied on model_a_TF_C_model_a_e
2025-11-24 15:59:35,945 - PyCoMo - INFO - worker 29560: Loop correction will be applied on model_a_TF_D_model_a_e
2025-11-24 15:59:35,956 - PyCoMo - INFO - worker 25520: Loop correction will be applied on model_b_TF_A_model_b_e
2025-11-24 15:59:35,958 - PyCoMo - INFO - worker 24196: Loop correction will be applied on model_b_TF_B_model_b_e
2025-11-24 15:59:35,959 - PyCoMo - INFO - worker 10380: Loop correction will be applied on model_b_TF_C_model_b_e
2025-11-24 15:59:35,961 - PyCoMo - INFO - worker 32444: Loop correction will be applied on model_b_TF_D_model_b_e
2025-11-24 15:59:35,976 - PyCoMo - INFO - Processed 100.0% of fva steps
CPU times: total: 125 ms
Wall time: 3.64 s
[11]:
reaction_id min_flux max_flux
model_a_TF_A_model_a_e model_a_TF_A_model_a_e 0.0 0.0
model_a_TF_B_model_a_e model_a_TF_B_model_a_e 0.0 0.0
model_a_TF_C_model_a_e model_a_TF_C_model_a_e 0.0 0.0
model_a_TF_D_model_a_e model_a_TF_D_model_a_e 0.0 0.0
EX_A_medium EX_A_medium 0.0 0.0
EX_B_medium EX_B_medium 0.0 0.0
EX_C_medium EX_C_medium 0.0 0.0
EX_D_medium EX_D_medium 0.0 0.0
model_b_TF_A_model_b_e model_b_TF_A_model_b_e 0.0 0.0
model_b_TF_B_model_b_e model_b_TF_B_model_b_e 0.0 0.0
model_b_TF_C_model_b_e model_b_TF_C_model_b_e 0.0 0.0
model_b_TF_D_model_b_e model_b_TF_D_model_b_e 0.0 0.0

With loopless FVA, PyCoMo correctly calculates 0 flux for all reactions, when no medium is present.

[12]:
%%time
com_model_obj_toy.medium = {'EX_A_medium': 1000.0, 'EX_D_medium': 1000.0}
com_model_obj_toy.apply_medium()
with com_model_obj_toy.model:
    fva = com_model_obj_toy.run_fva(fraction_of_optimum=1., loopless=True)
fva
[<Reaction model_a_TF_A_model_a_e at 0x1d4ba03a950>, <Reaction model_a_TF_B_model_a_e at 0x1d4ba03ab10>, <Reaction model_a_TF_C_model_a_e at 0x1d4ba03ac90>, <Reaction model_a_TF_D_model_a_e at 0x1d4ba03ae10>, <Reaction EX_A_medium at 0x1d4b9dbb550>, <Reaction EX_B_medium at 0x1d4b9ee12d0>, <Reaction EX_C_medium at 0x1d4ba04a410>, <Reaction EX_D_medium at 0x1d4b9f703d0>, <Reaction model_b_TF_A_model_b_e at 0x1d4ba07f210>, <Reaction model_b_TF_B_model_b_e at 0x1d4ba0e8790>, <Reaction model_b_TF_C_model_b_e at 0x1d4ba0e8850>, <Reaction model_b_TF_D_model_b_e at 0x1d4ba0e8950>]
2025-11-24 15:59:39,497 - PyCoMo - INFO - worker 25412: Loop correction will be applied on model_a_TF_A_model_a_e
2025-11-24 15:59:39,500 - PyCoMo - INFO - worker 15196: Loop correction will be applied on model_a_TF_B_model_a_e
2025-11-24 15:59:39,502 - PyCoMo - INFO - worker 25412: Loop correction will be applied on model_a_TF_C_model_a_e
2025-11-24 15:59:39,526 - PyCoMo - INFO - worker 5156: Loop correction will be applied on model_a_TF_D_model_a_e
2025-11-24 15:59:39,532 - PyCoMo - INFO - worker 25412: Loop correction will be applied on model_b_TF_A_model_b_e
2025-11-24 15:59:39,534 - PyCoMo - INFO - worker 27260: Loop correction will be applied on model_b_TF_B_model_b_e
2025-11-24 15:59:39,535 - PyCoMo - INFO - worker 34084: Loop correction will be applied on model_b_TF_C_model_b_e
2025-11-24 15:59:39,537 - PyCoMo - INFO - worker 15196: Loop correction will be applied on model_b_TF_D_model_b_e
2025-11-24 15:59:39,549 - PyCoMo - INFO - Processed 100.0% of fva steps
CPU times: total: 93.8 ms
Wall time: 3.56 s
[12]:
reaction_id min_flux max_flux
model_a_TF_A_model_a_e model_a_TF_A_model_a_e 5.000000e+02 5.000000e+02
model_a_TF_B_model_a_e model_a_TF_B_model_a_e 7.340937e-14 -7.340937e-14
model_a_TF_C_model_a_e model_a_TF_C_model_a_e 0.000000e+00 0.000000e+00
model_a_TF_D_model_a_e model_a_TF_D_model_a_e 5.000000e+02 5.000000e+02
EX_A_medium EX_A_medium -1.000000e+03 -1.000000e+03
EX_B_medium EX_B_medium 0.000000e+00 -1.312518e-13
EX_C_medium EX_C_medium 0.000000e+00 -2.313698e-13
EX_D_medium EX_D_medium -1.000000e+03 -1.000000e+03
model_b_TF_A_model_b_e model_b_TF_A_model_b_e 5.000000e+02 5.000000e+02
model_b_TF_B_model_b_e model_b_TF_B_model_b_e 7.340937e-14 -7.340937e-14
model_b_TF_C_model_b_e model_b_TF_C_model_b_e 0.000000e+00 0.000000e+00
model_b_TF_D_model_b_e model_b_TF_D_model_b_e 5.000000e+02 5.000000e+02

At maximum growht-rate, the reactions that are part of loops are not used, as this would lead to a decrease in growth-rate.

[13]:
%%time
com_model_obj_toy.medium = {'EX_A_medium': 1000.0, 'EX_D_medium': 1000.0}
com_model_obj_toy.apply_medium()
with com_model_obj_toy.model:
    fva = com_model_obj_toy.run_fva(fraction_of_optimum=0., loopless=True)
fva
[<Reaction model_a_TF_A_model_a_e at 0x1d4ba03a950>, <Reaction model_a_TF_B_model_a_e at 0x1d4ba03ab10>, <Reaction model_a_TF_C_model_a_e at 0x1d4ba03ac90>, <Reaction model_a_TF_D_model_a_e at 0x1d4ba03ae10>, <Reaction EX_A_medium at 0x1d4b9dbb550>, <Reaction EX_B_medium at 0x1d4b9ee12d0>, <Reaction EX_C_medium at 0x1d4ba04a410>, <Reaction EX_D_medium at 0x1d4b9f703d0>, <Reaction model_b_TF_A_model_b_e at 0x1d4ba07f210>, <Reaction model_b_TF_B_model_b_e at 0x1d4ba0e8790>, <Reaction model_b_TF_C_model_b_e at 0x1d4ba0e8850>, <Reaction model_b_TF_D_model_b_e at 0x1d4ba0e8950>]
2025-11-24 15:59:43,088 - PyCoMo - INFO - worker 32888: Loop correction will be applied on model_a_TF_A_model_a_e
2025-11-24 15:59:43,089 - PyCoMo - INFO - worker 29008: Loop correction will be applied on model_a_TF_B_model_a_e
2025-11-24 15:59:43,092 - PyCoMo - INFO - worker 21896: Loop correction will be applied on model_a_TF_C_model_a_e
2025-11-24 15:59:43,097 - PyCoMo - INFO - worker 32888: Loop correction will be applied on model_a_TF_D_model_a_e
2025-11-24 15:59:43,101 - PyCoMo - INFO - worker 29008: Loop correction will be applied on model_b_TF_A_model_b_e
2025-11-24 15:59:43,104 - PyCoMo - INFO - worker 21896: Loop correction will be applied on model_b_TF_B_model_b_e
2025-11-24 15:59:43,107 - PyCoMo - INFO - worker 32888: Loop correction will be applied on model_b_TF_C_model_b_e
2025-11-24 15:59:43,124 - PyCoMo - INFO - worker 29008: Loop correction will be applied on model_b_TF_D_model_b_e
2025-11-24 15:59:43,136 - PyCoMo - INFO - Processed 100.0% of fva steps
CPU times: total: 46.9 ms
Wall time: 3.57 s
[13]:
reaction_id min_flux max_flux
model_a_TF_A_model_a_e model_a_TF_A_model_a_e 0.0 5.000000e+02
model_a_TF_B_model_a_e model_a_TF_B_model_a_e -250.0 2.500000e+02
model_a_TF_C_model_a_e model_a_TF_C_model_a_e -250.0 2.500000e+02
model_a_TF_D_model_a_e model_a_TF_D_model_a_e 0.0 5.000000e+02
EX_A_medium EX_A_medium -1000.0 1.136868e-13
EX_B_medium EX_B_medium 0.0 5.000000e+02
EX_C_medium EX_C_medium 0.0 5.000000e+02
EX_D_medium EX_D_medium -1000.0 0.000000e+00
model_b_TF_A_model_b_e model_b_TF_A_model_b_e 0.0 5.000000e+02
model_b_TF_B_model_b_e model_b_TF_B_model_b_e -250.0 2.500000e+02
model_b_TF_C_model_b_e model_b_TF_C_model_b_e -250.0 2.500000e+02
model_b_TF_D_model_b_e model_b_TF_D_model_b_e 0.0 5.000000e+02

However, there are valid flux configurations where the “loopy” reactions can carry flux, with both directions being viable without the use of loops.

[14]:
%%time
com_model_obj_toy.medium = {'EX_A_medium': 1000.0, 'EX_D_medium': 1000.0}
com_model_obj_toy.apply_medium()
with com_model_obj_toy.model:
    fva = com_model_obj_toy.run_fva(fraction_of_optimum=0.8, loopless=True)
fva
[<Reaction model_a_TF_A_model_a_e at 0x1d4ba03a950>, <Reaction model_a_TF_B_model_a_e at 0x1d4ba03ab10>, <Reaction model_a_TF_C_model_a_e at 0x1d4ba03ac90>, <Reaction model_a_TF_D_model_a_e at 0x1d4ba03ae10>, <Reaction EX_A_medium at 0x1d4b9dbb550>, <Reaction EX_B_medium at 0x1d4b9ee12d0>, <Reaction EX_C_medium at 0x1d4ba04a410>, <Reaction EX_D_medium at 0x1d4b9f703d0>, <Reaction model_b_TF_A_model_b_e at 0x1d4ba07f210>, <Reaction model_b_TF_B_model_b_e at 0x1d4ba0e8790>, <Reaction model_b_TF_C_model_b_e at 0x1d4ba0e8850>, <Reaction model_b_TF_D_model_b_e at 0x1d4ba0e8950>]
2025-11-24 15:59:46,690 - PyCoMo - INFO - worker 2676: Loop correction will be applied on model_a_TF_A_model_a_e
2025-11-24 15:59:46,693 - PyCoMo - INFO - worker 25740: Loop correction will be applied on model_a_TF_B_model_a_e
2025-11-24 15:59:46,694 - PyCoMo - INFO - worker 8020: Loop correction will be applied on model_a_TF_C_model_a_e
2025-11-24 15:59:46,696 - PyCoMo - INFO - worker 17608: Loop correction will be applied on model_a_TF_D_model_a_e
2025-11-24 15:59:46,704 - PyCoMo - INFO - worker 17608: Loop correction will be applied on model_b_TF_A_model_b_e
2025-11-24 15:59:46,704 - PyCoMo - INFO - worker 30180: Loop correction will be applied on model_b_TF_B_model_b_e
2025-11-24 15:59:46,705 - PyCoMo - INFO - worker 24420: Loop correction will be applied on model_b_TF_C_model_b_e
2025-11-24 15:59:46,707 - PyCoMo - INFO - worker 8052: Loop correction will be applied on model_b_TF_D_model_b_e
2025-11-24 15:59:46,722 - PyCoMo - INFO - Processed 100.0% of fva steps
CPU times: total: 62.5 ms
Wall time: 3.57 s
[14]:
reaction_id min_flux max_flux
model_a_TF_A_model_a_e model_a_TF_A_model_a_e 300.0 500.0
model_a_TF_B_model_a_e model_a_TF_B_model_a_e -200.0 200.0
model_a_TF_C_model_a_e model_a_TF_C_model_a_e -100.0 100.0
model_a_TF_D_model_a_e model_a_TF_D_model_a_e 400.0 500.0
EX_A_medium EX_A_medium -1000.0 -800.0
EX_B_medium EX_B_medium 0.0 200.0
EX_C_medium EX_C_medium 0.0 200.0
EX_D_medium EX_D_medium -1000.0 -800.0
model_b_TF_A_model_b_e model_b_TF_A_model_b_e 300.0 500.0
model_b_TF_B_model_b_e model_b_TF_B_model_b_e -200.0 200.0
model_b_TF_C_model_b_e model_b_TF_C_model_b_e -100.0 100.0
model_b_TF_D_model_b_e model_b_TF_D_model_b_e 400.0 500.0

The example of 80% of maximum growth-rate shows, that the loopless solution scales down the flux of the “loopy” reactions, to the level where they can be active without decreasing the maximum growth-rate below the 80% threshold.

[15]:
%%time
com_model_obj_toy.medium = {'EX_A_medium': 1000.0, 'EX_D_medium': 1000.0}
com_model_obj_toy.apply_medium()
with com_model_obj_toy.model:
    fva = com_model_obj_toy.run_fva(composition_agnostic=True, fraction_of_optimum=0., loopless=True, processes=1)
fva
2025-11-24 15:59:46,896 - PyCoMo - INFO - Loop correction will be applied on model_a_TF_A_model_a_e
2025-11-24 15:59:46,906 - PyCoMo - INFO - Loop correction will be applied on model_a_TF_B_model_a_e
2025-11-24 15:59:46,915 - PyCoMo - INFO - Loop correction will be applied on model_a_TF_C_model_a_e
2025-11-24 15:59:46,926 - PyCoMo - INFO - Loop correction will be applied on model_a_TF_D_model_a_e
2025-11-24 15:59:46,942 - PyCoMo - INFO - Loop correction will be applied on model_b_TF_A_model_b_e
[<Reaction model_a_TF_A_model_a_e at 0x1d4ba03a950>, <Reaction model_a_TF_B_model_a_e at 0x1d4ba03ab10>, <Reaction model_a_TF_C_model_a_e at 0x1d4ba03ac90>, <Reaction model_a_TF_D_model_a_e at 0x1d4ba03ae10>, <Reaction EX_A_medium at 0x1d4b9dbb550>, <Reaction EX_B_medium at 0x1d4b9ee12d0>, <Reaction EX_C_medium at 0x1d4ba04a410>, <Reaction EX_D_medium at 0x1d4b9f703d0>, <Reaction model_b_TF_A_model_b_e at 0x1d4ba07f210>, <Reaction model_b_TF_B_model_b_e at 0x1d4ba0e8790>, <Reaction model_b_TF_C_model_b_e at 0x1d4ba0e8850>, <Reaction model_b_TF_D_model_b_e at 0x1d4ba0e8950>]
2025-11-24 15:59:46,951 - PyCoMo - INFO - Loop correction will be applied on model_b_TF_B_model_b_e
2025-11-24 15:59:46,960 - PyCoMo - INFO - Loop correction will be applied on model_b_TF_C_model_b_e
2025-11-24 15:59:46,972 - PyCoMo - INFO - Loop correction will be applied on model_b_TF_D_model_b_e
2025-11-24 15:59:46,982 - PyCoMo - INFO - Processed 100.0% of fva steps
CPU times: total: 125 ms
Wall time: 220 ms
[15]:
reaction_id min_flux max_flux
model_a_TF_A_model_a_e model_a_TF_A_model_a_e 0.0 1000.000000
model_a_TF_B_model_a_e model_a_TF_B_model_a_e -500.0 166.666667
model_a_TF_C_model_a_e model_a_TF_C_model_a_e -500.0 166.666667
model_a_TF_D_model_a_e model_a_TF_D_model_a_e 0.0 1000.000000
EX_A_medium EX_A_medium -1000.0 0.000000
EX_B_medium EX_B_medium 0.0 500.000000
EX_C_medium EX_C_medium 0.0 500.000000
EX_D_medium EX_D_medium -1000.0 0.000000
model_b_TF_A_model_b_e model_b_TF_A_model_b_e 0.0 1000.000000
model_b_TF_B_model_b_e model_b_TF_B_model_b_e -500.0 333.333333
model_b_TF_C_model_b_e model_b_TF_C_model_b_e -500.0 333.333333
model_b_TF_D_model_b_e model_b_TF_D_model_b_e 0.0 1000.000000

Using the composition agnostic approach to find all potential cross-feeding interactions shows the same pattern of feasible flux directions as with 0% fraction of optimum FVA, but with higher flux ranges due to the relaxed constraints.

This demonstrates that no other flux directions are feasible, even when considering all abundance profiles and not just 50/50 for model a and model b.